FluxInator / static /components /AlbumManager.js
Scalino84
Initial commit
1e7308f
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