Project-Image-Uploader/frontend/src/Components/Pages/ModerationGroupsPage.js
matthias.lotz 5ba463427b feat(frontend): upgrade react-router-dom 5→6 (Phase 3)
- Update package.json: react-router-dom ^5.2.0→^6.28.0
- Migrate App.js: Switch→Routes, component→element props, path="*" for 404
- Migrate 5 pages: useHistory→useNavigate, history.push()→navigate()
  - GroupsOverviewPage.js (4x navigate)
  - ModerationGroupsPage.js (1x navigate)
  - ModerationGroupImagesPage.js (2x navigate)
  - PublicGroupImagesPage.js (import updated)
  - SlideshowPage.js (4x navigate + keyboard handler)
- Regenerate package-lock.json with react-router v6

 Tested: Production build 254.46 KB gzip (+1.17 KB)
 Manual test: Navigation, moderation routing, slideshow ESC - all working

Phase 3 complete: Modern react-router v6 with improved routing API.
2025-10-28 22:59:59 +01:00

273 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { useNavigate } from 'react-router-dom';
import { Container } from '@material-ui/core';
import Navbar from '../ComponentUtils/Headers/Navbar';
import Footer from '../ComponentUtils/Footer';
import ImageGallery from '../ComponentUtils/ImageGallery';
const ModerationGroupsPage = () => {
const [groups, setGroups] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [selectedGroup, setSelectedGroup] = useState(null);
const [showImages, setShowImages] = useState(false);
const navigate = useNavigate();
useEffect(() => {
loadModerationGroups();
}, []);
const loadModerationGroups = async () => {
try {
setLoading(true);
const response = await fetch('/moderation/groups');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setGroups(data.groups);
} catch (error) {
console.error('Fehler beim Laden der Moderations-Gruppen:', error);
setError('Fehler beim Laden der Gruppen');
} finally {
setLoading(false);
}
};
const approveGroup = async (groupId, approved) => {
try {
const response = await fetch(`/groups/${groupId}/approve`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ approved: approved })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Update local state
setGroups(groups.map(group =>
group.groupId === groupId
? { ...group, approved: approved }
: group
));
} catch (error) {
console.error('Fehler beim Freigeben der Gruppe:', error);
alert('Fehler beim Freigeben der Gruppe');
}
};
const deleteImage = async (groupId, imageId) => {
console.log('deleteImage called with:', { groupId, imageId });
console.log('API_URL:', window._env_.API_URL);
try {
// Use relative URL to go through Nginx proxy
const url = `/groups/${groupId}/images/${imageId}`;
console.log('DELETE request to:', url);
const response = await fetch(url, {
method: 'DELETE'
});
console.log('Response status:', response.status);
console.log('Response ok:', response.ok);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Remove image from selectedGroup
if (selectedGroup && selectedGroup.groupId === groupId) {
const updatedImages = selectedGroup.images.filter(img => img.id !== imageId);
setSelectedGroup({
...selectedGroup,
images: updatedImages,
imageCount: updatedImages.length
});
}
// Update group image count
setGroups(groups.map(group =>
group.groupId === groupId
? { ...group, imageCount: group.imageCount - 1 }
: group
));
} catch (error) {
console.error('Fehler beim Löschen des Bildes:', error);
console.error('Error details:', error.message, error.stack);
alert('Fehler beim Löschen des Bildes: ' + error.message);
}
};
const deleteGroup = async (groupId) => {
if (!window.confirm('Gruppe wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) {
return;
}
try {
const response = await fetch(`/groups/${groupId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
setGroups(groups.filter(group => group.groupId !== groupId));
if (selectedGroup && selectedGroup.groupId === groupId) {
setSelectedGroup(null);
setShowImages(false);
}
} catch (error) {
console.error('Fehler beim Löschen der Gruppe:', error);
alert('Fehler beim Löschen der Gruppe');
}
};
// Navigate to the dedicated group images page
const viewGroupImages = (group) => {
navigate(`/moderation/groups/${group.groupId}`);
};
if (loading) {
return <div className="moderation-loading">Lade Gruppen...</div>;
}
if (error) {
return <div className="moderation-error">{error}</div>;
}
const pendingGroups = groups.filter(g => !g.approved);
const approvedGroups = groups.filter(g => g.approved);
return (
<div className="allContainer">
<Navbar />
<Helmet>
<title>Moderation - Interne Verwaltung</title>
<meta name="robots" content="noindex, nofollow, nosnippet, noarchive" />
<meta name="googlebot" content="noindex, nofollow" />
<meta name="description" content="Interne Moderationsseite - Nicht öffentlich zugänglich" />
</Helmet>
<Container className="moderation-content" maxWidth="lg" style={{ paddingTop: '20px' }}>
<h1>Moderation</h1>
<div className="moderation-stats">
<div className="stat-item">
<span className="stat-number">{pendingGroups.length}</span>
<span className="stat-label">Wartend</span>
</div>
<div className="stat-item">
<span className="stat-number">{approvedGroups.length}</span>
<span className="stat-label">Freigegeben</span>
</div>
<div className="stat-item">
<span className="stat-number">{groups.length}</span>
<span className="stat-label">Gesamt</span>
</div>
</div>
{/* Wartende Gruppen */}
<section className="moderation-section">
<ImageGallery
items={pendingGroups}
title={`🔍 Wartende Freigabe (${pendingGroups.length})`}
onApprove={approveGroup}
onViewImages={viewGroupImages}
onDelete={deleteGroup}
isPending={true}
mode="moderation"
emptyMessage="Keine wartenden Gruppen"
/>
</section>
{/* Freigegebene Gruppen */}
<section className="moderation-section">
<ImageGallery
items={approvedGroups}
title={`✅ Freigegebene Gruppen (${approvedGroups.length})`}
onApprove={approveGroup}
onViewImages={viewGroupImages}
onDelete={deleteGroup}
isPending={false}
mode="moderation"
emptyMessage="Keine freigegebenen Gruppen"
/>
</section>
{/* Bilder-Modal */}
{showImages && selectedGroup && (
<ImageModal
group={selectedGroup}
onClose={() => {
setShowImages(false);
setSelectedGroup(null);
}}
onDeleteImage={deleteImage}
/>
)}
</Container>
<div className="footerContainer"><Footer /></div>
</div>
);
};
// `GroupCard` has been extracted to `../ComponentUtils/GroupCard`
const ImageModal = ({ group, onClose, onDeleteImage }) => {
return (
<div className="image-modal-overlay" onClick={onClose}>
<div className="image-modal" onClick={e => e.stopPropagation()}>
<div className="modal-header">
<h2>{group.title}</h2>
<button className="close-btn" onClick={onClose}>×</button>
</div>
<div className="modal-body">
<div className="group-details">
<p><strong>Jahr:</strong> {group.year}</p>
<p><strong>Ersteller:</strong> {group.name}</p>
{group.description && (
<p><strong>Beschreibung:</strong> {group.description}</p>
)}
<p><strong>Bilder:</strong> {group.images.length}</p>
</div>
<div className="images-grid">
{group.images.map(image => (
<div key={image.id} className="image-item">
<img
src={`/download/${image.fileName}`}
alt={image.originalName}
className="modal-image"
/>
<div className="image-actions">
<span className="image-name">{image.originalName}</span>
<button
className="btn btn-danger btn-sm"
onClick={() => onDeleteImage(group.groupId, image.id)}
title="Bild löschen"
>
🗑
</button>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default ModerationGroupsPage;