Spaces:
Runtime error
Runtime error
import React, { useState, useEffect, useCallback } from 'react'; | |
import { API, APIHandler, Validators } from './api.js'; | |
const AlbumManager = () => { | |
const [albums, setAlbums] = useState([]); | |
const [loading, setLoading] = useState(true); | |
const [error, setError] = useState(null); | |
const [selectedAlbums, setSelectedAlbums] = useState(new Set()); | |
const [editingAlbum, setEditingAlbum] = useState(null); | |
// Daten laden | |
const fetchAlbums = useCallback(async () => { | |
try { | |
setLoading(true); | |
const data = await APIHandler.get(API.albums.list); | |
setAlbums(data); | |
setError(null); | |
} catch (err) { | |
setError('Fehler beim Laden der Alben: ' + err.message); | |
console.error('Fetch error:', err); | |
} finally { | |
setLoading(false); | |
} | |
}, []); | |
useEffect(() => { | |
fetchAlbums(); | |
}, [fetchAlbums]); | |
// Album erstellen | |
const handleCreate = async (name, description = '') => { | |
try { | |
//const validationErrors = Validators.album({ name }); | |
//if (Object.keys(validationErrors).length > 0) { | |
// throw new Error(Object.values(validationErrors).join(', ')); | |
//} | |
await APIHandler.post(API.albums.create, { name, description }); | |
await fetchAlbums(); | |
window.showToast('Erfolg', 'Album wurde erstellt', 'success'); | |
} catch (err) { | |
window.showToast('Fehler', err.message, 'danger'); | |
} | |
}; | |
// Album aktualisieren | |
const handleUpdate = async (id, updates) => { | |
try { | |
//const validationErrors = Validators.album(updates); | |
//if (Object.keys(validationErrors).length > 0) { | |
// throw new Error(Object.values(validationErrors).join(', ')); | |
//} | |
await APIHandler.put(API.albums.update(id), updates); | |
await fetchAlbums(); | |
setEditingAlbum(null); | |
window.showToast('Erfolg', 'Album wurde aktualisiert', 'success'); | |
} catch (err) { | |
window.showToast('Fehler', err.message, 'danger'); | |
} | |
}; | |
// Album aktualisieren | |
const handleUpdate = async (id, updates) => { | |
try { | |
const validationErrors = Validators.album(updates); | |
if (Object.keys(validationErrors).length > 0) { | |
throw new Error(Object.values(validationErrors).join(', ')); | |
} | |
await APIHandler.put(API.albums.update(id), updates); | |
await fetchAlbums(); | |
setEditingAlbum(null); | |
window.showToast('Erfolg', 'Album wurde aktualisiert', 'success'); | |
} catch (err) { | |
window.showToast('Fehler', err.message, 'danger'); | |
} | |
}; | |
// Massenbearbeitung | |
const handleBulkDelete = async () => { | |
if (selectedAlbums.size === 0) return; | |
if (!window.confirm(`Möchten Sie ${selectedAlbums.size} Alben wirklich löschen?`)) return; | |
try { | |
await Promise.all( | |
Array.from(selectedAlbums).map(id => | |
APIHandler.delete(API.albums.delete(id)) | |
) | |
); | |
setSelectedAlbums(new Set()); | |
await fetchAlbums(); | |
window.showToast('Erfolg', 'Ausgewählte Alben wurden gelöscht', 'success'); | |
} catch (err) { | |
window.showToast('Fehler', err.message, 'danger'); | |
} | |
}; | |
const handleSelectAll = (event) => { | |
if (event.target.checked) { | |
setSelectedAlbums(new Set(albums.map(album => album.id))); | |
} else { | |
setSelectedAlbums(new Set()); | |
} | |
}; | |
if (loading) { | |
return ( | |
<div className="d-flex justify-content-center p-5"> | |
<div className="spinner-border text-primary" role="status"> | |
<span className="visually-hidden">Laden...</span> | |
</div> | |
</div> | |
); | |
} | |
if (error) { | |
return ( | |
<div className="alert alert-danger m-3" role="alert"> | |
<h4 className="alert-heading">Fehler</h4> | |
<p>{error}</p> | |
<button | |
className="btn btn-outline-danger" | |
onClick={fetchAlbums} | |
> | |
Erneut versuchen | |
</button> | |
</div> | |
); | |
} | |
return ( | |
<div className="card-body"> | |
{/* Toolbar */} | |
<div className="d-flex justify-content-between mb-3"> | |
<button | |
className="btn btn-primary" | |
onClick={() => setEditingAlbum({ name: '', description: '' })} | |
> | |
<i className="bi bi-plus-lg"></i> Neues Album | |
</button> | |
{selectedAlbums.size > 0 && ( | |
<button | |
className="btn btn-danger" | |
onClick={handleBulkDelete} | |
> | |
<i className="bi bi-trash"></i> | |
{selectedAlbums.size} Alben löschen | |
</button> | |
)} | |
</div> | |
{/* Album Liste */} | |
<div className="table-responsive"> | |
<table className="table table-hover"> | |
<thead> | |
<tr> | |
<th> | |
<input | |
type="checkbox" | |
className="form-check-input" | |
onChange={handleSelectAll} | |
checked={selectedAlbums.size === albums.length} | |
/> | |
</th> | |
<th>Name</th> | |
<th>Bilder</th> | |
<th>Erstellt</th> | |
<th>Aktionen</th> | |
</tr> | |
</thead> | |
<tbody> | |
{albums.map(album => ( | |
<tr key={album.id}> | |
<td> | |
<input | |
type="checkbox" | |
className="form-check-input" | |
checked={selectedAlbums.has(album.id)} | |
onChange={(e) => { | |
const newSelected = new Set(selectedAlbums); | |
if (e.target.checked) { | |
newSelected.add(album.id); | |
} else { | |
newSelected.delete(album.id); | |
} | |
setSelectedAlbums(newSelected); | |
}} | |
/> | |
</td> | |
<td> | |
{editingAlbum?.id === album.id ? ( | |
<input | |
type="text" | |
className="form-control" | |
value={editingAlbum.name} | |
onChange={(e) => setEditingAlbum({ | |
...editingAlbum, | |
name: e.target.value | |
})} | |
/> | |
) : album.name} | |
</td> | |
<td>{album.imageCount}</td> | |
<td>{new Date(album.createdAt).toLocaleDateString()}</td> | |
<td> | |
<div className="btn-group"> | |
{editingAlbum?.id === album.id ? ( | |
<> | |
<button | |
className="btn btn-sm btn-success" | |
onClick={() => handleUpdate(album.id, editingAlbum)} | |
> | |
<i className="bi bi-check"></i> | |
</button> | |
<button | |
className="btn btn-sm btn-secondary" | |
onClick={() => setEditingAlbum(null)} | |
> | |
<i className="bi bi-x"></i> | |
</button> | |
</> | |
) : ( | |
<> | |
<button | |
className="btn btn-sm btn-outline-primary" | |
onClick={() => setEditingAlbum(album)} | |
> | |
<i className="bi bi-pencil"></i> | |
</button> | |
<button | |
className="btn btn-sm btn-outline-danger" | |
onClick={() => handleDelete(album.id)} | |
> | |
<i className="bi bi-trash"></i> | |
</button> | |
</> | |
)} | |
</div> | |
</td> | |
</tr> | |
))} | |
</tbody> | |
</table> | |
</div> | |
{albums.length === 0 && ( | |
<div className="text-center text-muted p-5"> | |
<i className="bi bi-folder-x display-4"></i> | |
<p className="mt-3">Keine Alben vorhanden</p> | |
</div> | |
)} | |
</div> | |
); | |
}; | |
export default AlbumManager; | |
window.AlbumManager = AlbumManager; // <-- Diese Zeile ans Ende setzen | |