From e4712f9e7e56d331d2335e1f8e94162b2c203a97 Mon Sep 17 00:00:00 2001 From: "matthias.lotz" Date: Sat, 29 Nov 2025 15:21:51 +0100 Subject: [PATCH] refactor: Extract ConsentFilter and StatsDisplay components from ModerationGroupsPage - Created ConsentFilter component with proper styling - Created StatsDisplay component for statistics display - Added ModerationGroupsPage.css to remove inline styles - Removed 83 lines of inline CSS from ModerationGroupsPage - Components now reusable across admin pages - Added container wrappers and titles to both components - Improved code maintainability and separation of concerns --- .../ConsentFilter/ConsentFilter.css | 54 ++++++ .../ConsentFilter/ConsentFilter.js | 80 ++++++++ .../StatsDisplay/StatsDisplay.css | 46 +++++ .../StatsDisplay/StatsDisplay.js | 26 +++ .../Pages/Css/ModerationGroupsPage.css | 179 ++++++++++++++++++ .../Components/Pages/ModerationGroupsPage.js | 110 ++++------- 6 files changed, 418 insertions(+), 77 deletions(-) create mode 100644 frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.css create mode 100644 frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.js create mode 100644 frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.css create mode 100644 frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.js create mode 100644 frontend/src/Components/Pages/Css/ModerationGroupsPage.css diff --git a/frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.css b/frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.css new file mode 100644 index 0000000..feadb03 --- /dev/null +++ b/frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.css @@ -0,0 +1,54 @@ +.consent-filter-container { + margin-bottom: 24px; +} + +.consent-filter-title { + margin-bottom: 16px; + border-bottom: 2px solid #e9ecef; + font-size: 1.5rem; + font-weight: 600; + color: #333; +} + +.consent-filter { + min-width: 250px; + border: 1px solid #ccc; + border-radius: 8px; + padding: 16px; +} + +.consent-filter-legend { + display: flex; + align-items: center; + margin-bottom: 8px; + font-size: 14px; + font-weight: 600; +} + +.filter-icon { + margin-right: 4px; + font-size: 18px; +} + +.consent-filter-options { + display: flex; + flex-direction: column; + gap: 8px; +} + +.consent-filter-label { + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + transition: color 0.2s; +} + +.consent-filter-label:hover { + color: #4CAF50; +} + +.consent-filter-checkbox { + margin-right: 8px; + cursor: pointer; +} diff --git a/frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.js b/frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.js new file mode 100644 index 0000000..d6f784d --- /dev/null +++ b/frontend/src/Components/ComponentUtils/ConsentFilter/ConsentFilter.js @@ -0,0 +1,80 @@ +import React from 'react'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import './ConsentFilter.css'; + +/** + * ConsentFilter Component + * Displays checkboxes for filtering groups by consent type + * + * @param {Object} filters - Current filter state { workshop, facebook, instagram, tiktok } + * @param {Function} onChange - Callback when filter changes + * @param {Array} platforms - Available social media platforms from API + */ +const ConsentFilter = ({ filters, onChange, platforms = [] }) => { + const handleCheckboxChange = (filterName, checked) => { + onChange({ + ...filters, + [filterName]: checked + }); + }; + + // Platform mapping for display names + const platformLabels = { + workshop: 'Werkstatt', + facebook: 'Facebook', + instagram: 'Instagram', + tiktok: 'TikTok' + }; + + return ( +
+

Filter

+
+ + + Consent-Filter + +
+ + + + +
+
+
+ ); +}; + +export default ConsentFilter; diff --git a/frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.css b/frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.css new file mode 100644 index 0000000..219e298 --- /dev/null +++ b/frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.css @@ -0,0 +1,46 @@ +.stats-display-container { + margin-bottom: 24px; +} + +.stats-title { + margin-bottom: 16px; + border-bottom: 2px solid #e9ecef; + font-size: 1.5rem; + font-weight: 600; + color: #333; +} + +.stats-display { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 16px; +} + +.stat-item { + color: white; + padding: 24px; + border-radius: 12px; + text-align: center; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.stat-item:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); +} + +.stat-number { + display: block; + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 8px; +} + +.stat-label { + display: block; + font-size: 0.95rem; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.95; +} diff --git a/frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.js b/frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.js new file mode 100644 index 0000000..b6f5e2f --- /dev/null +++ b/frontend/src/Components/ComponentUtils/StatsDisplay/StatsDisplay.js @@ -0,0 +1,26 @@ +import React from 'react'; +import './StatsDisplay.css'; + +/** + * StatsDisplay Component + * Displays statistics in a grid layout + * + * @param {Array} stats - Array of stat objects { number, label } + */ +const StatsDisplay = ({ stats }) => { + return ( +
+

Statistiken

+
+ {stats.map((stat, index) => ( +
+ {stat.number} + {stat.label} +
+ ))} +
+
+ ); +}; + +export default StatsDisplay; diff --git a/frontend/src/Components/Pages/Css/ModerationGroupsPage.css b/frontend/src/Components/Pages/Css/ModerationGroupsPage.css new file mode 100644 index 0000000..3670b47 --- /dev/null +++ b/frontend/src/Components/Pages/Css/ModerationGroupsPage.css @@ -0,0 +1,179 @@ +/* Moderation Page Layout */ +.moderation-content { + padding-top: 20px; +} + +.moderation-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 24px; +} + +.moderation-user-info { + display: flex; + align-items: center; + gap: 16px; +} + +.moderation-username { + color: #666666; + margin: 0; + font-size: 14px; +} + +/* Filter Controls Area */ +.moderation-controls { + display: flex; + gap: 16px; + margin-bottom: 24px; + align-items: flex-start; + flex-wrap: wrap; +} + +/* Sections */ +.moderation-section { + margin-bottom: 32px; +} + +/* Loading and Error States */ +.moderation-loading, +.moderation-error { + text-align: center; + padding: 40px; + font-size: 18px; +} + +.moderation-error { + color: #d32f2f; + background-color: #ffebee; + border-radius: 8px; +} + +/* Image Modal */ +.image-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.image-modal { + background: white; + border-radius: 12px; + max-width: 90%; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid #e0e0e0; +} + +.modal-header h2 { + margin: 0; + font-size: 1.5rem; +} + +.close-btn { + background: none; + border: none; + font-size: 2rem; + cursor: pointer; + color: #666; + line-height: 1; + padding: 0; + width: 32px; + height: 32px; + transition: color 0.2s; +} + +.close-btn:hover { + color: #d32f2f; +} + +.modal-body { + padding: 20px; +} + +.group-details { + margin-bottom: 20px; + padding: 16px; + background-color: #f5f5f5; + border-radius: 8px; +} + +.group-details p { + margin: 8px 0; +} + +.images-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; +} + +.image-item { + border: 1px solid #e0e0e0; + border-radius: 8px; + overflow: hidden; + transition: transform 0.2s, box-shadow 0.2s; +} + +.image-item:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.modal-image { + width: 100%; + height: 200px; + object-fit: cover; +} + +.image-actions { + padding: 12px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: #fafafa; +} + +.image-name { + font-size: 12px; + color: #666; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + margin-right: 8px; +} + +/* Responsive */ +@media (max-width: 768px) { + .moderation-header { + flex-direction: column; + align-items: flex-start; + } + + .images-grid { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } + + .image-modal { + max-width: 95%; + } +} diff --git a/frontend/src/Components/Pages/ModerationGroupsPage.js b/frontend/src/Components/Pages/ModerationGroupsPage.js index 45b59a9..6f264a7 100644 --- a/frontend/src/Components/Pages/ModerationGroupsPage.js +++ b/frontend/src/Components/Pages/ModerationGroupsPage.js @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Helmet } from 'react-helmet'; import { useNavigate } from 'react-router-dom'; -import FilterListIcon from '@mui/icons-material/FilterList'; import Swal from 'sweetalert2/dist/sweetalert2.js'; // Services @@ -16,8 +15,14 @@ import Navbar from '../ComponentUtils/Headers/Navbar'; import Footer from '../ComponentUtils/Footer'; import ImageGallery from '../ComponentUtils/ImageGallery'; import DeletionLogSection from '../ComponentUtils/DeletionLogSection'; +import ConsentFilter from '../ComponentUtils/ConsentFilter/ConsentFilter'; +import StatsDisplay from '../ComponentUtils/StatsDisplay/StatsDisplay'; import { getImageSrc } from '../../Utils/imageUtils'; +// Styles +import './Css/ModerationGroupsPage.css'; +import '../../App.css'; + const ModerationGroupsPage = () => { const [groups, setGroups] = useState([]); const [loading, setLoading] = useState(true); @@ -267,15 +272,17 @@ const ModerationGroupsPage = () => { -
-
+
+

Moderation

-
+
+ {user?.username && ( -

+

Eingeloggt als {user.username}

)} + +
- -
-
- {pendingGroups.length} - Wartend -
-
- {approvedGroups.length} - Freigegeben -
-
- {groups.length} - Gesamt -
-
+ {/* Lösch-Historie */} +
+ +
+ {/* Filter und Export Controls */} -
-
- - - Consent-Filter - -
- - - - -
-
- - -
+ + + + {/* Wartende Gruppen */}
@@ -386,10 +345,7 @@ const ModerationGroupsPage = () => { />
- {/* Lösch-Historie */} -
- -
+ {/* Bilder-Modal */} {showImages && selectedGroup && (