refactor: Centralized styling with CSS and global MUI overrides

- Migrated all Pages from Material-UI to HTML+CSS (GroupsOverviewPage, ManagementPortalPage, ModerationGroupImagesPage, ModerationGroupsPage, PublicGroupImagesPage, SlideshowPage, MultiUploadPage)
- Added comprehensive typography system in App.css (h1-h3, p, utility classes)
- Added global Material-UI font overrides for Open Sans
- Removed redundant fontFamily: 'roboto' from all components
- Fixed button alignment in ImageGalleryCard (margin-top: auto)
- Removed emojis from titles for cleaner UI
- Standardized button padding (12px 30px) across application
- Improved code consistency and maintainability with centralized CSS approach
This commit is contained in:
Matthias Lotz 2025-11-27 19:47:39 +01:00
parent 25dda32c4e
commit 215acaa67f
13 changed files with 444 additions and 375 deletions

View File

@ -1,5 +1,193 @@
/* Main shared styles for cards, buttons, modals used across pages */ /* Main shared styles for cards, buttons, modals used across pages */
/* ============================================
TYPOGRAPHY - Zentrale Schrift-Definitionen
============================================ */
body {
font-family: 'Open Sans', sans-serif;
color: #333333;
line-height: 1.6;
}
h1, .h1 {
font-family: 'Open Sans', sans-serif;
font-weight: 500;
font-size: 28px;
color: #333333;
margin-bottom: 10px;
}
h2, .h2 {
font-family: 'Open Sans', sans-serif;
font-weight: 500;
font-size: 24px;
color: #333333;
margin-bottom: 15px;
}
h3, .h3 {
font-family: 'Open Sans', sans-serif;
font-weight: 500;
font-size: 20px;
color: #333333;
margin-bottom: 12px;
}
p, .text-body {
font-family: 'Open Sans', sans-serif;
font-weight: 400;
font-size: 16px;
color: #666666;
margin-bottom: 16px;
}
.text-subtitle {
font-family: 'Open Sans', sans-serif;
font-weight: 300;
font-size: 16px;
color: #666666;
}
.text-small {
font-size: 14px;
color: #666666;
}
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
/* ============================================
LAYOUT & CONTAINERS
============================================ */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
padding: 20px;
margin-bottom: 20px;
}
.card-content {
padding: 20px;
}
/* ============================================
PAGE HEADERS
============================================ */
.page-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
font-family: 'Open Sans', sans-serif;
font-weight: 500;
font-size: 28px;
color: #333333;
text-align: center;
margin-bottom: 10px;
}
.page-subtitle {
font-family: 'Open Sans', sans-serif;
font-weight: 300;
font-size: 16px;
color: #666666;
text-align: center;
margin-bottom: 30px;
}
/* ============================================
UTILITY CLASSES
============================================ */
.flex-center {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
margin-top: 30px;
}
.text-center-block {
text-align: center;
padding: 40px 0;
}
/* Spacing utilities */
.mt-1 { margin-top: 8px; }
.mt-2 { margin-top: 16px; }
.mt-3 { margin-top: 24px; }
.mt-4 { margin-top: 32px; }
.mb-1 { margin-bottom: 8px; }
.mb-2 { margin-bottom: 16px; }
.mb-3 { margin-bottom: 24px; }
.mb-4 { margin-bottom: 32px; }
.p-2 { padding: 16px; }
.p-3 { padding: 24px; }
/* ============================================
SUCCESS BOX (Upload Success)
============================================ */
.success-box {
margin-top: 32px;
padding: 24px;
border-radius: 12px;
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
color: white;
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.4);
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.success-box h2 {
font-size: 28px;
font-weight: bold;
margin-bottom: 8px;
color: white;
}
.success-box p {
font-size: 18px;
margin-bottom: 16px;
color: white;
}
.info-box {
background: rgba(255,255,255,0.2);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.info-box-highlight {
background: rgba(255,255,255,0.95);
border-radius: 8px;
padding: 20px;
margin-bottom: 16px;
border: 2px solid rgba(255,255,255,0.3);
}
/* ============================================
EXISTING STYLES BELOW
============================================ */
/* Page-specific styles for GroupsOverviewPage */ /* Page-specific styles for GroupsOverviewPage */
.groups-overview-container { padding-top: 20px; padding-bottom: 40px; min-height: 80vh; } .groups-overview-container { padding-top: 20px; padding-bottom: 40px; min-height: 80vh; }
.header-card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 30px; text-align: center; padding: 20px; } .header-card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 30px; text-align: center; padding: 20px; }
@ -52,7 +240,7 @@ p { font-family: 'Open Sans', sans-serif; color:#555; line-height:1.6; }
} }
/* Buttons */ /* Buttons */
.btn { padding:8px 12px; border:none; border-radius:6px; cursor:pointer; font-size:0.85rem; transition:background-color 0.2s; flex:1; min-width:80px; } .btn { padding:12px 30px; border:none; border-radius:6px; cursor:pointer; font-size:16px; transition:background-color 0.2s; min-width:80px; }
.btn-secondary { background:#6c757d; color:white; } .btn-secondary { background:#6c757d; color:white; }
.btn-secondary:hover { background:#5a6268; } .btn-secondary:hover { background:#5a6268; }
.btn-outline-secondary { background:transparent; border:1px solid #6c757d; color:#6c757d; } .btn-outline-secondary { background:transparent; border:1px solid #6c757d; color:#6c757d; }
@ -63,7 +251,6 @@ p { font-family: 'Open Sans', sans-serif; color:#555; line-height:1.6; }
.btn-warning:hover { background:#e0a800; } .btn-warning:hover { background:#e0a800; }
.btn-danger { background:#dc3545; color:white; } .btn-danger { background:#dc3545; color:white; }
.btn-danger:hover { background:#c82333; } .btn-danger:hover { background:#c82333; }
.btn-sm { padding:4px 8px; font-size:0.75rem; min-width:auto; }
.btn:disabled { opacity:0.65; cursor:not-allowed; } .btn:disabled { opacity:0.65; cursor:not-allowed; }
/* Modal */ /* Modal */
@ -104,3 +291,32 @@ p { font-family: 'Open Sans', sans-serif; color:#555; line-height:1.6; }
.admin-auth-card { width:100%; max-width:420px; box-shadow:0 8px 24px rgba(0,0,0,0.08); } .admin-auth-card { width:100%; max-width:420px; box-shadow:0 8px 24px rgba(0,0,0,0.08); }
.admin-auth-form { width:100%; } .admin-auth-form { width:100%; }
.admin-auth-error { max-width:420px; background:#fff3f3; border:1px solid #ffcdd2; padding:24px; border-radius:12px; text-align:center; color:#b71c1c; } .admin-auth-error { max-width:420px; background:#fff3f3; border:1px solid #ffcdd2; padding:24px; border-radius:12px; text-align:center; color:#b71c1c; }
/* ============================================
MATERIAL-UI OVERRIDES - Globale Schriftart
============================================ */
/* TextField, Input, Textarea */
.MuiTextField-root input,
.MuiTextField-root textarea,
.MuiInputBase-root,
.MuiInputBase-input,
.MuiOutlinedInput-input {
font-family: 'Open Sans', sans-serif !important;
}
/* Labels */
.MuiFormLabel-root,
.MuiInputLabel-root,
.MuiTypography-root {
font-family: 'Open Sans', sans-serif !important;
}
/* Buttons */
.MuiButton-root {
font-family: 'Open Sans', sans-serif !important;
}
/* Checkbox Labels */
.MuiFormControlLabel-label {
font-family: 'Open Sans', sans-serif !important;
}

View File

@ -112,6 +112,8 @@
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
flex-direction: column;
margin-top: auto;
} }
/* ImageGalleryCard - No preview state */ /* ImageGalleryCard - No preview state */

View File

@ -144,7 +144,7 @@ function GroupMetadataEditor({
> >
{/* Component Header */} {/* Component Header */}
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600, mb: 2 }}> <Typography variant="h6" gutterBottom sx={{ fontWeight: 600, mb: 2 }}>
📝 Projekt-Informationen Projekt-Informationen
</Typography> </Typography>
<DescriptionInput <DescriptionInput

View File

@ -221,71 +221,30 @@ const ImageGalleryCard = ({
mode === 'preview' ? ( mode === 'preview' ? (
// Preview mode actions (for upload preview) // Preview mode actions (for upload preview)
<> <>
<button <button className="btn btn-danger" onClick={() => onDelete(itemId)}>🗑 Löschen</button>
className="btn btn-danger"
onClick={() => onDelete(itemId)}
>
🗑 Löschen
</button>
{!isEditMode ? ( {!isEditMode ? (
<button <button className="btn btn-primary" onClick={() => onEditMode?.(true)}> Edit </button>
className="btn btn-primary btn-sm"
onClick={() => onEditMode?.(true)}
>
Edit
</button>
) : ( ) : (
<button <button className="btn btn-success" onClick={() => onEditMode?.(false)}> Fertig</button>
className="btn btn-success btn-sm"
onClick={() => onEditMode?.(false)}
>
Fertig
</button>
)} )}
</> </>
) : ( ) : (
// Moderation mode actions (for existing groups) // Moderation mode actions (for existing groups)
<> <>
<button <button className="btn btn-secondary" onClick={() => onViewImages(item)}> Gruppe editieren</button>
className="btn btn-secondary"
onClick={() => onViewImages(item)}
>
Gruppe editieren
</button>
{isPending ? ( {isPending ? (
<button <button className="btn btn-success" onClick={() => onApprove(itemId, true)}> Freigeben</button>
className="btn btn-success"
onClick={() => onApprove(itemId, true)}
>
Freigeben
</button>
) : ( ) : (
<button <button className="btn btn-warning" onClick={() => onApprove(itemId, false)}> Sperren</button>
className="btn btn-warning"
onClick={() => onApprove(itemId, false)}
>
Sperren
</button>
)} )}
<button <button className="btn btn-danger" onClick={() => onDelete(itemId)}>🗑 Löschen</button>
className="btn btn-danger"
onClick={() => onDelete(itemId)}
>
🗑 Löschen
</button>
</> </>
) )
) : mode !== 'single-image' ? ( ) : mode !== 'single-image' ? (
// Public view mode (only for group cards, not single images) // Public view mode (only for group cards, not single images)
<button <button className="view-button" onClick={() => onViewImages(item)} title="Anzeigen">Anzeigen</button>
className="view-button"
onClick={() => onViewImages(item)}
title="Anzeigen"
>
Anzeigen
</button>
) : null} ) : null}
</div> </div>
)} )}

View File

@ -17,7 +17,6 @@ function DescriptionInput({
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const fieldLabelSx = { const fieldLabelSx = {
fontFamily: 'roboto',
fontSize: '14px', fontSize: '14px',
color: '#555555', color: '#555555',
marginBottom: '8px', marginBottom: '8px',
@ -25,7 +24,6 @@ function DescriptionInput({
}; };
const sectionTitleSx = { const sectionTitleSx = {
fontFamily: 'roboto',
fontSize: '18px', fontSize: '18px',
color: '#333333', color: '#333333',
marginBottom: '15px', marginBottom: '15px',
@ -68,7 +66,7 @@ function DescriptionInput({
}; };
const requiredIndicatorSx = { color: '#E57373', fontSize: '16px' }; const requiredIndicatorSx = { color: '#E57373', fontSize: '16px' };
const optionalIndicatorSx = { color: '#9E9E9E', fontSize: '12px', fontStyle: 'italic' }; const optionalIndicatorSx = { color: '#9E9E9E', fontSize: '12px' };
return ( return (
<Box sx={{ marginTop: '20px', marginBottom: '20px' }}> <Box sx={{ marginTop: '20px', marginBottom: '20px' }}>

View File

@ -77,15 +77,13 @@ function MultiImageDropzone({ onImagesSelected, selectedImages = [] }) {
const dropzoneTextSx = { const dropzoneTextSx = {
fontSize: '18px', fontSize: '18px',
fontFamily: 'roboto',
color: '#666666', color: '#666666',
margin: '10px 0' margin: '10px 0'
}; };
const dropzoneSubtextSx = { const dropzoneSubtextSx = {
fontSize: '14px', fontSize: '14px',
color: '#999999', color: '#999999'
fontFamily: 'roboto'
}; };
const fileCountSx = { const fileCountSx = {
@ -106,7 +104,7 @@ function MultiImageDropzone({ onImagesSelected, selectedImages = [] }) {
onClick={handleClick} onClick={handleClick}
> >
<Typography sx={dropzoneTextSx}> <Typography sx={dropzoneTextSx}>
📸 Mehrere Bilder hier hinziehen oder klicken zum Auswählen Mehrere Bilder hier hinziehen oder klicken zum Auswählen
</Typography> </Typography>
<Typography sx={dropzoneSubtextSx}> <Typography sx={dropzoneSubtextSx}>

View File

@ -1,13 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import {
Container,
Card,
Typography,
Box,
CircularProgress
} from '@mui/material';
@ -63,14 +56,14 @@ function GroupsOverviewPage() {
return ( return (
<div className="allContainer"> <div className="allContainer">
<Navbar /> <Navbar />
<Container maxWidth="lg" className="page-container"> <div className="container">
<div className="loading-container"> <div className="flex-center" style={{ minHeight: '400px' }}>
<CircularProgress size={60} color="primary" /> <div className="text-center">
<Typography variant="h6" style={{ marginTop: '20px', color: '#666666' }}> <div className="loading-spinner" style={{ width: '60px', height: '60px', border: '4px solid #f3f3f3', borderTop: '4px solid #3498db', borderRadius: '50%', animation: 'spin 1s linear infinite', margin: '0 auto' }}></div>
Slideshows werden geladen... <p className="mt-3" style={{ color: '#666666' }}>Slideshows werden geladen...</p>
</Typography> </div>
</div>
</div> </div>
</Container>
<Footer /> <Footer />
</div> </div>
); );
@ -86,53 +79,39 @@ function GroupsOverviewPage() {
</Helmet> </Helmet>
<Navbar /> <Navbar />
<Container maxWidth="lg" className="page-container"> <div className="container page-container">
{/* Header */} {/* Header */}
<Card className="header-card"> <div className="card">
<Typography className="header-title"> <h1 className="page-title">Alle Slideshows</h1>
Alle Slideshows <p className="page-subtitle">Übersicht aller erstellten Slideshows.</p>
</Typography> </div>
<Typography className="header-subtitle">
Übersicht aller erstellten Slideshows.
</Typography>
</Card>
{/* Groups Grid */} {/* Groups Grid */}
{error ? ( {error ? (
<div className="empty-state"> <div className="empty-state">
<Typography variant="h6" style={{ color: '#f44336', marginBottom: '20px' }}> <h2 style={{ color: '#f44336' }} className="mb-3">😕 Fehler beim Laden</h2>
😕 Fehler beim Laden <p style={{ color: '#666666' }} className="mb-4">{error}</p>
</Typography>
<Typography variant="body1" style={{ color: '#666666', marginBottom: '30px' }}>
{error}
</Typography>
<button onClick={loadGroups} className="btn btn-secondary"> <button onClick={loadGroups} className="btn btn-secondary">
🔄 Erneut versuchen 🔄 Erneut versuchen
</button> </button>
</div> </div>
) : groups.length === 0 ? ( ) : groups.length === 0 ? (
<div className="empty-state"> <div className="empty-state">
<Typography variant="h4" style={{ color: '#666666', marginBottom: '20px' }}> <h2 style={{ color: '#666666' }} className="mb-3">📸 Keine Slideshows vorhanden</h2>
📸 Keine Slideshows vorhanden <p style={{ color: '#999999' }} className="mb-4">
</Typography>
<Typography variant="body1" style={{ color: '#999999', marginBottom: '30px' }}>
Erstellen Sie Ihre erste Slideshow, indem Sie mehrere Bilder hochladen. Erstellen Sie Ihre erste Slideshow, indem Sie mehrere Bilder hochladen.
</Typography> </p>
<button <button className="btn btn-success" onClick={handleCreateNew}>
className="btn btn-success"
onClick={handleCreateNew}
style={{ fontSize: '16px', padding: '12px 24px' }}
>
Erste Slideshow erstellen Erste Slideshow erstellen
</button> </button>
</div> </div>
) : ( ) : (
<> <>
<Box marginBottom={2}> <div className="mb-3">
<Typography variant="h6" style={{ color: '#666666' }}> <h3 style={{ color: '#666666' }}>
📊 {groups.length} Slideshow{groups.length !== 1 ? 's' : ''} gefunden {groups.length} Slideshow{groups.length !== 1 ? 's' : ''} gefunden
</Typography> </h3>
</Box> </div>
<ImageGallery <ImageGallery
items={groups} items={groups}
onViewImages={(group) => handleViewGroup(group.groupId)} onViewImages={(group) => handleViewGroup(group.groupId)}
@ -142,7 +121,7 @@ function GroupsOverviewPage() {
/> />
</> </>
)} )}
</Container> </div>
<div className="footerContainer"> <div className="footerContainer">
<Footer /> <Footer />

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { Container, Card, CardContent, Typography, Box, Button } from '@mui/material';
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
import NavbarUpload from '../ComponentUtils/Headers/NavbarUpload'; import NavbarUpload from '../ComponentUtils/Headers/NavbarUpload';
import Footer from '../ComponentUtils/Footer'; import Footer from '../ComponentUtils/Footer';
@ -180,9 +179,9 @@ function ManagementPortalPage() {
return ( return (
<div className="allContainer"> <div className="allContainer">
<NavbarUpload /> <NavbarUpload />
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}> <div className="container flex-center" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
<Loading /> <Loading />
</Container> </div>
<Footer /> <Footer />
</div> </div>
); );
@ -192,19 +191,15 @@ function ManagementPortalPage() {
return ( return (
<div className="allContainer"> <div className="allContainer">
<NavbarUpload /> <NavbarUpload />
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}> <div className="container" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', textAlign: 'center' }}> <div className="card text-center">
<Typography variant="h5" color="error" gutterBottom> <h2 style={{ color: '#f44336' }} className="mb-2">{error}</h2>
{error} <p style={{ color: '#666666' }} className="mb-4">{error}</p>
</Typography> <button className="btn btn-primary" onClick={() => navigate('/')}>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
{error}
</Typography>
<Button variant="contained" onClick={() => navigate('/')}>
Zur Startseite Zur Startseite
</Button> </button>
</Card> </div>
</Container> </div>
<Footer /> <Footer />
</div> </div>
); );
@ -214,19 +209,17 @@ function ManagementPortalPage() {
<div className="allContainer"> <div className="allContainer">
<NavbarUpload /> <NavbarUpload />
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}> <div className="container" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', mb: '20px' }}> <div className="card mb-3">
<CardContent> <div className="card-content">
<Typography sx={{ fontFamily: 'roboto', fontWeight: 400, fontSize: '28px', textAlign: 'center', mb: '10px', color: '#333333' }}> <h1 className="page-title text-center mb-2">Mein Upload verwalten</h1>
Mein Upload verwalten <p className="page-subtitle text-center mb-4">
</Typography>
<Typography sx={{ fontFamily: 'roboto', fontWeight: 300, fontSize: '16px', color: '#666666', textAlign: 'center', mb: '30px' }}>
Hier können Sie Ihre hochgeladenen Bilder verwalten, Metadaten bearbeiten und Einwilligungen ändern. Hier können Sie Ihre hochgeladenen Bilder verwalten, Metadaten bearbeiten und Einwilligungen ändern.
</Typography> </p>
{/* Group Overview */} {/* Group Overview */}
{group && ( {group && (
<Box sx={{ mb: 3 }}> <div className="mb-4">
<ImageGalleryCard <ImageGalleryCard
item={group} item={group}
showActions={false} showActions={false}
@ -235,29 +228,25 @@ function ManagementPortalPage() {
hidePreview={true} hidePreview={true}
/> />
<Box sx={{ mt: 2 }}> <div className="mt-3">
<Typography variant="subtitle2" gutterBottom sx={{ fontWeight: 600 }}> <h3 className="text-small" style={{ fontWeight: 600 }}>Erteilte Einwilligungen:</h3>
Erteilte Einwilligungen:
</Typography>
<ConsentBadges group={group} /> <ConsentBadges group={group} />
</Box> </div>
</Box> </div>
)} )}
{/* Add Images Dropzone */} {/* Add Images Dropzone */}
<Box sx={{ mb: 3 }}> <div className="mb-4">
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}> <h3 className="mb-2" style={{ fontWeight: 600 }}>Weitere Bilder hinzufügen</h3>
Weitere Bilder hinzufügen
</Typography>
<MultiImageDropzone <MultiImageDropzone
onImagesSelected={handleImagesSelected} onImagesSelected={handleImagesSelected}
selectedImages={[]} selectedImages={[]}
/> />
</Box> </div>
{/* Image Descriptions Manager */} {/* Image Descriptions Manager */}
{group && group.images && group.images.length > 0 && ( {group && group.images && group.images.length > 0 && (
<Box sx={{ mb: 3 }}> <div className="mb-4">
<ImageDescriptionManager <ImageDescriptionManager
images={group.images} images={group.images}
token={token} token={token}
@ -265,44 +254,44 @@ function ManagementPortalPage() {
onReorder={handleReorder} onReorder={handleReorder}
onRefresh={loadGroup} onRefresh={loadGroup}
/> />
</Box> </div>
)} )}
{/* Group Metadata Editor */} {/* Group Metadata Editor */}
{group && ( {group && (
<Box sx={{ mb: 3 }}> <div className="mb-4">
<GroupMetadataEditor <GroupMetadataEditor
initialMetadata={group.metadata} initialMetadata={group.metadata}
token={token} token={token}
onRefresh={loadGroup} onRefresh={loadGroup}
/> />
</Box> </div>
)} )}
{/* Consent Manager */} {/* Consent Manager */}
{group && ( {group && (
<Box sx={{ mb: 3 }}> <div className="mb-4">
<ConsentManager <ConsentManager
initialConsents={group.consents} initialConsents={group.consents}
token={token} token={token}
groupId={group.groupId} groupId={group.groupId}
onRefresh={loadGroup} onRefresh={loadGroup}
/> />
</Box> </div>
)} )}
{/* Delete Group Button */} {/* Delete Group Button */}
{group && ( {group && (
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}> <div className="mt-4 flex-center">
<DeleteGroupButton <DeleteGroupButton
token={token} token={token}
groupName={group.title || group.name || 'diese Gruppe'} groupName={group.title || group.name || 'diese Gruppe'}
/> />
</Box> </div>
)} )}
</CardContent> </div>
</Card> </div>
</Container> </div>
<div className="footerContainer"> <div className="footerContainer">
<Footer /> <Footer />

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { Container, Box } from '@mui/material';
// Services // Services
import { adminGet } from '../../services/adminApi'; import { adminGet } from '../../services/adminApi';
@ -81,7 +80,7 @@ const ModerationGroupImagesPage = () => {
<div className="allContainer"> <div className="allContainer">
<Navbar /> <Navbar />
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}> <div className="container" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
{/* Image Descriptions Manager */} {/* Image Descriptions Manager */}
<ImageDescriptionManager <ImageDescriptionManager
images={group.images} images={group.images}
@ -99,15 +98,15 @@ const ModerationGroupImagesPage = () => {
/> />
{/* Back Button */} {/* Back Button */}
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}> <div className="flex-center mt-4">
<button <button
className="btn btn-secondary" className="btn btn-secondary"
onClick={() => navigate('/moderation')} onClick={() => navigate('/moderation')}
> >
Zurück zur Übersicht Zurück zur Übersicht
</button> </button>
</Box> </div>
</Container> </div>
<div className="footerContainer"><Footer /></div> <div className="footerContainer"><Footer /></div>
</div> </div>

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Container, Box, FormControl, FormLabel, FormGroup, FormControlLabel, Checkbox, Typography } from '@mui/material';
import FilterListIcon from '@mui/icons-material/FilterList'; import FilterListIcon from '@mui/icons-material/FilterList';
import Swal from 'sweetalert2/dist/sweetalert2.js'; import Swal from 'sweetalert2/dist/sweetalert2.js';
@ -268,23 +267,14 @@ const ModerationGroupsPage = () => {
<meta name="description" content="Interne Moderationsseite - Nicht öffentlich zugänglich" /> <meta name="description" content="Interne Moderationsseite - Nicht öffentlich zugänglich" />
</Helmet> </Helmet>
<Container className="moderation-content" maxWidth="lg" style={{ paddingTop: '20px' }}> <div className="container moderation-content" style={{ paddingTop: '20px' }}>
<Box sx={{ <div className="flex-center" style={{ justifyContent: 'space-between', flexWrap: 'wrap', gap: '16px', marginBottom: '24px' }}>
display: 'flex', <h1>Moderation</h1>
alignItems: 'center', <div className="flex-center" style={{ gap: '16px' }}>
justifyContent: 'space-between',
flexWrap: 'wrap',
gap: 2,
mb: 3
}}>
<Typography variant="h4" component="h1">
Moderation
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{user?.username && ( {user?.username && (
<Typography variant="body2" color="text.secondary"> <p className="text-small" style={{ color: '#666666', margin: 0 }}>
Eingeloggt als <strong>{user.username}</strong> Eingeloggt als <strong>{user.username}</strong>
</Typography> </p>
)} )}
<button <button
type="button" type="button"
@ -295,8 +285,8 @@ const ModerationGroupsPage = () => {
> >
{logoutPending ? 'Wird abgemeldet…' : 'Logout'} {logoutPending ? 'Wird abgemeldet…' : 'Logout'}
</button> </button>
</Box> </div>
</Box> </div>
<div className="moderation-stats"> <div className="moderation-stats">
<div className="stat-item"> <div className="stat-item">
@ -314,79 +304,65 @@ const ModerationGroupsPage = () => {
</div> </div>
{/* Filter und Export Controls */} {/* Filter und Export Controls */}
<Box sx={{ <div style={{ display: 'flex', gap: '16px', marginBottom: '24px', alignItems: 'flex-start', flexWrap: 'wrap' }}>
display: 'flex', <fieldset style={{ minWidth: '250px', border: '1px solid #ccc', borderRadius: '8px', padding: '16px' }}>
gap: 2, <legend style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', fontSize: '14px', fontWeight: 600 }}>
mb: 3, <FilterListIcon style={{ marginRight: '4px', fontSize: '18px' }} />
alignItems: 'center',
flexWrap: 'wrap'
}}>
<FormControl component="fieldset" sx={{ minWidth: 250 }}>
<FormLabel component="legend" sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<FilterListIcon sx={{ mr: 0.5, fontSize: 18 }} />
Consent-Filter Consent-Filter
</FormLabel> </legend>
<FormGroup> <div>
<FormControlLabel <label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
control={ <input
<Checkbox type="checkbox"
checked={consentFilters.workshop} checked={consentFilters.workshop}
onChange={(e) => setConsentFilters({...consentFilters, workshop: e.target.checked})} onChange={(e) => setConsentFilters({...consentFilters, workshop: e.target.checked})}
size="small" style={{ marginRight: '8px' }}
/> />
} Werkstatt
label="Werkstatt" </label>
/> <label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
<FormControlLabel <input
control={ type="checkbox"
<Checkbox
checked={consentFilters.facebook} checked={consentFilters.facebook}
onChange={(e) => setConsentFilters({...consentFilters, facebook: e.target.checked})} onChange={(e) => setConsentFilters({...consentFilters, facebook: e.target.checked})}
size="small" style={{ marginRight: '8px' }}
/> />
} Facebook
label="Facebook" </label>
/> <label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
<FormControlLabel <input
control={ type="checkbox"
<Checkbox
checked={consentFilters.instagram} checked={consentFilters.instagram}
onChange={(e) => setConsentFilters({...consentFilters, instagram: e.target.checked})} onChange={(e) => setConsentFilters({...consentFilters, instagram: e.target.checked})}
size="small" style={{ marginRight: '8px' }}
/> />
} Instagram
label="Instagram" </label>
/> <label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
<FormControlLabel <input
control={ type="checkbox"
<Checkbox
checked={consentFilters.tiktok} checked={consentFilters.tiktok}
onChange={(e) => setConsentFilters({...consentFilters, tiktok: e.target.checked})} onChange={(e) => setConsentFilters({...consentFilters, tiktok: e.target.checked})}
size="small" style={{ marginRight: '8px' }}
/> />
} TikTok
label="TikTok" </label>
/> </div>
</FormGroup> </fieldset>
</FormControl>
<button <button
className="btn btn-success" className="btn btn-success"
onClick={exportConsentData} onClick={exportConsentData}
style={{
fontSize: '14px',
padding: '10px 20px'
}}
> >
📥 Consent-Daten exportieren Consent-Daten exportieren
</button> </button>
</Box> </div>
{/* Wartende Gruppen */} {/* Wartende Gruppen */}
<section className="moderation-section"> <section className="moderation-section">
<ImageGallery <ImageGallery
items={pendingGroups} items={pendingGroups}
title={`🔍 Wartende Freigabe (${pendingGroups.length})`} title={`Wartende Freigabe (${pendingGroups.length})`}
onApprove={approveGroup} onApprove={approveGroup}
onViewImages={viewGroupImages} onViewImages={viewGroupImages}
onDelete={deleteGroup} onDelete={deleteGroup}
@ -400,7 +376,7 @@ const ModerationGroupsPage = () => {
<section className="moderation-section"> <section className="moderation-section">
<ImageGallery <ImageGallery
items={approvedGroups} items={approvedGroups}
title={`Freigegebene Gruppen (${approvedGroups.length})`} title={`Freigegebene Gruppen (${approvedGroups.length})`}
onApprove={approveGroup} onApprove={approveGroup}
onViewImages={viewGroupImages} onViewImages={viewGroupImages}
onDelete={deleteGroup} onDelete={deleteGroup}
@ -426,7 +402,7 @@ const ModerationGroupsPage = () => {
onDeleteImage={deleteImage} onDeleteImage={deleteImage}
/> />
)} )}
</Container> </div>
<div className="footerContainer"><Footer /></div> <div className="footerContainer"><Footer /></div>
</div> </div>
); );
@ -471,7 +447,7 @@ const ImageModal = ({ group, onClose, onDeleteImage }) => {
<div className="image-actions"> <div className="image-actions">
<span className="image-name">{image.originalName}</span> <span className="image-name">{image.originalName}</span>
<button <button
className="btn btn-danger btn-sm" className="btn btn-danger"
onClick={() => onDeleteImage(group.groupId, image.id)} onClick={() => onDeleteImage(group.groupId, image.id)}
title="Bild löschen" title="Bild löschen"
> >

View File

@ -1,5 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Card, CardContent, Typography, Container, Box } from '@mui/material';
// Components // Components
import NavbarUpload from '../ComponentUtils/Headers/NavbarUpload'; import NavbarUpload from '../ComponentUtils/Headers/NavbarUpload';
@ -163,17 +162,17 @@ function MultiUploadPage() {
<div className="allContainer"> <div className="allContainer">
{<NavbarUpload />} {<NavbarUpload />}
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}> <div className="container">
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', mb: '20px' }}> <div className="card">
<CardContent> <div className="card-content">
<Typography sx={{ fontFamily: 'roboto', fontWeight: 400, fontSize: '28px', textAlign: 'center', mb: '10px', color: '#333333' }}> <h1 className="page-title">
Project Image Uploader Project Image Uploader
</Typography> </h1>
<Typography sx={{ fontFamily: 'roboto', fontWeight: 300, fontSize: '16px', color: '#666666', textAlign: 'center', mb: '30px' }}> <p className="page-subtitle">
Lade ein oder mehrere Bilder von deinem Projekt hoch und beschreibe dein Projekt in wenigen Worten. Lade ein oder mehrere Bilder von deinem Projekt hoch und beschreibe dein Projekt in wenigen Worten.
<br /> <br />
Die Bilder werden nur hier im Hobbyhimmel auf dem Monitor gezeigt, es wird an keine Dritten weiter gegeben. Die Bilder werden nur hier im Hobbyhimmel auf dem Monitor gezeigt, es wird an keine Dritten weiter gegeben.
</Typography> </p>
{!uploading ? ( {!uploading ? (
<> <>
@ -215,15 +214,11 @@ function MultiUploadPage() {
/> />
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'center', mt: 3, flexWrap: 'wrap' }}> <div className="flex-center">
<button <button
className="btn btn-success" className="btn btn-success"
onClick={handleUpload} onClick={handleUpload}
disabled={!canUpload()} disabled={!canUpload()}
style={{
fontSize: '16px',
padding: '12px 30px'
}}
> >
🚀 {selectedImages.length} Bild{selectedImages.length !== 1 ? 'er' : ''} hochladen 🚀 {selectedImages.length} Bild{selectedImages.length !== 1 ? 'er' : ''} hochladen
</button> </button>
@ -231,14 +226,10 @@ function MultiUploadPage() {
<button <button
className="btn btn-secondary" className="btn btn-secondary"
onClick={handleClearAll} onClick={handleClearAll}
style={{
fontSize: '16px',
padding: '12px 30px'
}}
> >
🗑 Alle entfernen 🗑 Alle entfernen
</button> </button>
</Box> </div>
</> </>
)} )}
</> </>
@ -254,85 +245,58 @@ function MultiUploadPage() {
/> />
</> </>
) : ( ) : (
<Box sx={{ <div className="success-box">
mt: 4, <h2>
p: 3,
borderRadius: '12px',
background: 'linear-gradient(135deg, #4CAF50 0%, #45a049 100%)',
color: 'white',
boxShadow: '0 4px 20px rgba(76, 175, 80, 0.4)',
animation: 'slideIn 0.5s ease-out',
'@keyframes slideIn': {
from: {
opacity: 0,
transform: 'translateY(-20px)'
},
to: {
opacity: 1,
transform: 'translateY(0)'
}
}
}}>
<Typography sx={{ fontSize: '28px', fontWeight: 'bold', mb: 1 }}>
Upload erfolgreich! Upload erfolgreich!
</Typography> </h2>
<Typography sx={{ fontSize: '18px', mb: 2 }}> <p>
{uploadResult?.imageCount || 0} Bild{uploadResult?.imageCount === 1 ? '' : 'er'} {uploadResult?.imageCount === 1 ? 'wurde' : 'wurden'} hochgeladen. {uploadResult?.imageCount || 0} Bild{uploadResult?.imageCount === 1 ? '' : 'er'} {uploadResult?.imageCount === 1 ? 'wurde' : 'wurden'} hochgeladen.
</Typography> </p>
<Box sx={{ bgcolor: 'rgba(255,255,255,0.2)', borderRadius: '8px', p: 2, mb: 2 }}> <div className="info-box">
<Typography sx={{ fontSize: '14px', mb: 1 }}> <p className="text-small">
Ihre Referenz-Nummer: Ihre Referenz-Nummer:
</Typography> </p>
<Typography sx={{ fontSize: '20px', fontFamily: 'monospace', fontWeight: 'bold', mb: 1 }}> <p style={{ fontSize: '20px', fontFamily: 'monospace', fontWeight: 'bold', marginBottom: '8px' }}>
{uploadResult?.groupId} {uploadResult?.groupId}
</Typography> </p>
<Typography sx={{ fontSize: '12px', opacity: 0.9 }}> <p className="text-small" style={{ opacity: 0.9 }}>
Notieren Sie sich diese Nummer für spätere Anfragen an das Team. Notieren Sie sich diese Nummer für spätere Anfragen an das Team.
</Typography> </p>
</Box> </div>
{uploadResult?.managementToken && ( {uploadResult?.managementToken && (
<Box sx={{ <div className="info-box-highlight">
bgcolor: 'rgba(255,255,255,0.95)', <h3 style={{ fontSize: '16px', fontWeight: 'bold', marginBottom: '12px', color: '#2e7d32' }}>
borderRadius: '8px',
p: 2.5,
mb: 2,
border: '2px solid rgba(255,255,255,0.3)'
}}>
<Typography sx={{ fontSize: '16px', fontWeight: 'bold', mb: 1.5, color: '#2e7d32' }}>
🔗 Verwaltungslink für Ihren Upload 🔗 Verwaltungslink für Ihren Upload
</Typography> </h3>
<Typography sx={{ fontSize: '13px', mb: 1.5, color: '#333' }}> <p style={{ fontSize: '13px', marginBottom: '12px', color: '#333' }}>
Mit diesem Link können Sie später Ihre Bilder verwalten, Einwilligungen widerrufen oder die Gruppe löschen: Mit diesem Link können Sie später Ihre Bilder verwalten, Einwilligungen widerrufen oder die Gruppe löschen:
</Typography> </p>
<Box sx={{ <div style={{
bgcolor: '#f5f5f5', background: '#f5f5f5',
p: 1.5, padding: '12px',
borderRadius: '6px', borderRadius: '6px',
mb: 1.5, marginBottom: '12px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 1, gap: '8px',
flexWrap: 'wrap' flexWrap: 'wrap'
}}> }}>
<Typography sx={{ <p style={{
fontSize: '13px', fontSize: '13px',
fontFamily: 'monospace', fontFamily: 'monospace',
color: '#1976d2', color: '#1976d2',
wordBreak: 'break-all', wordBreak: 'break-all',
flex: 1, flex: 1,
minWidth: '200px' minWidth: '200px',
margin: 0
}}> }}>
{window.location.origin}/manage/{uploadResult.managementToken} {window.location.origin}/manage/{uploadResult.managementToken}
</Typography> </p>
<button <button
className="btn btn-secondary" className="btn btn-secondary"
style={{
fontSize: '12px',
padding: '6px 16px'
}}
onClick={() => { onClick={() => {
const link = `${window.location.origin}/manage/${uploadResult.managementToken}`; const link = `${window.location.origin}/manage/${uploadResult.managementToken}`;
// Fallback für HTTP (wenn navigator.clipboard nicht verfügbar) // Fallback für HTTP (wenn navigator.clipboard nicht verfügbar)
@ -351,43 +315,39 @@ function MultiUploadPage() {
> >
📋 Kopieren 📋 Kopieren
</button> </button>
</Box> </div>
<Typography sx={{ fontSize: '11px', color: '#666', mb: 0.5 }}> <p className="text-small" style={{ color: '#666', marginBottom: '4px' }}>
<strong>Wichtig:</strong> Bewahren Sie diesen Link sicher auf! Jeder mit diesem Link kann Ihren Upload verwalten. <strong>Wichtig:</strong> Bewahren Sie diesen Link sicher auf! Jeder mit diesem Link kann Ihren Upload verwalten.
</Typography> </p>
<Typography sx={{ fontSize: '11px', color: '#666', fontStyle: 'italic' }}> <p className="text-small" style={{ color: '#666', fontStyle: 'italic' }}>
<strong>Hinweis:</strong> Über diesen Link können Sie nur die Bilder in der Werkstatt verwalten. Bereits auf Social Media Plattformen veröffentlichte Bilder müssen separat dort gelöscht werden. <strong>Hinweis:</strong> Über diesen Link können Sie nur die Bilder in der Werkstatt verwalten. Bereits auf Social Media Plattformen veröffentlichte Bilder müssen separat dort gelöscht werden.
</Typography> </p>
</Box> </div>
)} )}
<Typography sx={{ fontSize: '13px', mb: 2, opacity: 0.95 }}> <p style={{ fontSize: '13px', marginBottom: '16px', opacity: 0.95 }}>
Die Bilder werden geprüft und nach Freigabe auf dem Werkstatt-Monitor angezeigt. Die Bilder werden geprüft und nach Freigabe auf dem Werkstatt-Monitor angezeigt.
{' '}Bei Social Media Einwilligung werden sie entsprechend veröffentlicht. {' '}Bei Social Media Einwilligung werden sie entsprechend veröffentlicht.
</Typography> </p>
<Typography sx={{ fontSize: '12px', mb: 3, opacity: 0.9 }}> <p style={{ fontSize: '12px', marginBottom: '24px', opacity: 0.9 }}>
<strong>Fragen oder Widerruf?</strong> Kontakt: <strong>it@hobbyhimmel.de</strong> <strong>Fragen oder Widerruf?</strong> Kontakt: <strong>it@hobbyhimmel.de</strong>
</Typography> </p>
<button <button
className="btn btn-success" className="btn btn-success"
style={{
fontSize: '16px',
padding: '12px 30px'
}}
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
> >
👍 Weitere Bilder hochladen 👍 Weitere Bilder hochladen
</button> </button>
</Box> </div>
)} )}
</div> </div>
)} )}
</CardContent> </div>
</Card> </div>
</Container> </div>
<div className="footerContainer"> <div className="footerContainer">
<Footer /> <Footer />

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Container } from '@mui/material';
import Navbar from '../ComponentUtils/Headers/Navbar'; import Navbar from '../ComponentUtils/Headers/Navbar';
import Footer from '../ComponentUtils/Footer'; import Footer from '../ComponentUtils/Footer';
import ImageGalleryCard from '../ComponentUtils/ImageGalleryCard'; import ImageGalleryCard from '../ComponentUtils/ImageGalleryCard';
@ -42,7 +41,7 @@ const PublicGroupImagesPage = () => {
<div className="allContainer"> <div className="allContainer">
<Navbar /> <Navbar />
<Container maxWidth="lg" className="page-container" style={{ marginTop: '40px' }}> <div className="container page-container" style={{ marginTop: '40px' }}>
<ImageGalleryCard <ImageGalleryCard
item={group} item={group}
showActions={false} showActions={false}
@ -70,7 +69,7 @@ const PublicGroupImagesPage = () => {
return acc; return acc;
}, {}) : {}} }, {}) : {}}
/> />
</Container> </div>
<div className="footerContainer"><Footer /></div> <div className="footerContainer"><Footer /></div>
</div> </div>

View File

@ -1,11 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import {
Typography,
Box,
CircularProgress,
IconButton
} from '@mui/material';
import { import {
Home as HomeIcon, Home as HomeIcon,
ExitToApp as ExitIcon ExitToApp as ExitIcon
@ -172,12 +166,12 @@ function SlideshowPage() {
if (loading) { if (loading) {
return ( return (
<Box sx={fullscreenSx}> <div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
<Box sx={loadingContainerSx}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', color: 'white' }}>
<CircularProgress sx={{ color: 'white', mb: 2 }} /> <div className="loading-spinner" style={{ width: '60px', height: '60px', border: '4px solid rgba(255,255,255,0.3)', borderTop: '4px solid white', borderRadius: '50%', animation: 'spin 1s linear infinite', marginBottom: '16px' }}></div>
<Typography sx={{ color: 'white' }}>Slideshow wird geladen...</Typography> <p style={{ color: 'white', margin: 0 }}>Slideshow wird geladen...</p>
</Box> </div>
</Box> </div>
); );
} }
@ -192,27 +186,27 @@ function SlideshowPage() {
if (error) { if (error) {
return ( return (
<Box sx={fullscreenSx}> <div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
<Box sx={loadingContainerSx}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', color: 'white' }}>
<Typography sx={{ color: 'white', fontSize: '24px' }}>{error}</Typography> <p style={{ color: 'white', fontSize: '24px', margin: 0 }}>{error}</p>
<IconButton sx={homeButtonSx} onClick={() => navigate('/')} title="Zur Startseite"> <button style={{ position: 'absolute', top: '20px', left: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Zur Startseite">
<HomeIcon /> <HomeIcon />
</IconButton> </button>
</Box> </div>
</Box> </div>
); );
} }
if (!currentGroup || !currentImage) { if (!currentGroup || !currentImage) {
return ( return (
<Box sx={fullscreenSx}> <div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
<Box sx={loadingContainerSx}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', color: 'white' }}>
<Typography sx={{ color: 'white', fontSize: '24px' }}>Keine Bilder verfügbar</Typography> <p style={{ color: 'white', fontSize: '24px', margin: 0 }}>Keine Bilder verfügbar</p>
<IconButton sx={homeButtonSx} onClick={() => navigate('/')} title="Zur Startseite"> <button style={{ position: 'absolute', top: '20px', left: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Zur Startseite">
<HomeIcon /> <HomeIcon />
</IconButton> </button>
</Box> </div>
</Box> </div>
); );
} }
@ -275,41 +269,41 @@ function SlideshowPage() {
const metaTextSx = { color: '#999', fontSize: '12px', mt: 1, fontFamily: 'roboto' }; const metaTextSx = { color: '#999', fontSize: '12px', mt: 1, fontFamily: 'roboto' };
return ( return (
<Box sx={fullscreenSx}> <div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
{/* Navigation Buttons */} {/* Navigation Buttons */}
<IconButton sx={homeButtonSx} onClick={() => navigate('/')} title="Zur Startseite"> <button style={{ position: 'absolute', top: '20px', left: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Zur Startseite" onMouseOver={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.8)'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.5)'}>
<HomeIcon /> <HomeIcon />
</IconButton> </button>
<IconButton sx={exitButtonSx} onClick={() => navigate('/')} title="Slideshow beenden"> <button style={{ position: 'absolute', top: '20px', right: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Slideshow beenden" onMouseOver={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.8)'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.5)'}>
<ExitIcon /> <ExitIcon />
</IconButton> </button>
{/* Hauptbild */} {/* Hauptbild */}
<Box component="img" src={getImageSrc(currentImage, false)} alt={currentImage.originalName} sx={{ ...slideshowImageSx, opacity: fadeOut ? 0 : 1 }} /> <img src={getImageSrc(currentImage, false)} alt={currentImage.originalName} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', transition: `opacity ${TRANSITION_TIME}ms ease-in-out`, opacity: fadeOut ? 0 : 1 }} />
{/* Bildbeschreibung (wenn vorhanden) */} {/* Bildbeschreibung (wenn vorhanden) */}
{currentImage.imageDescription && ( {currentImage.imageDescription && (
<Box sx={imageDescriptionSx}> <div style={{ position: 'fixed', bottom: '140px', left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0,0,0,0.7)', padding: '15px 30px', borderRadius: '8px', maxWidth: '80%', textAlign: 'center', backdropFilter: 'blur(5px)', zIndex: 10002 }}>
<Typography sx={imageDescriptionTextSx}>{currentImage.imageDescription}</Typography> <p style={{ color: 'white', fontSize: '18px', margin: 0, lineHeight: 1.4, fontFamily: 'Open Sans, sans-serif' }}>{currentImage.imageDescription}</p>
</Box> </div>
)} )}
{/* Beschreibung */} {/* Beschreibung */}
<Box sx={descriptionContainerSx}> <div style={{ position: 'fixed', left: '40px', bottom: '40px', backgroundColor: 'rgba(0,0,0,0.8)', padding: '25px 35px', borderRadius: '12px', maxWidth: '35vw', minWidth: '260px', textAlign: 'left', backdropFilter: 'blur(5px)', zIndex: 10001, boxShadow: '0 4px 24px rgba(0,0,0,0.4)' }}>
{/* Titel */} {/* Titel */}
<Typography sx={titleTextSx}>{currentGroup.title || 'Unbenanntes Projekt'}</Typography> <h2 style={{ color: 'white', fontSize: '28px', fontWeight: 500, marginBottom: '8px', marginTop: 0, fontFamily: 'Open Sans, sans-serif' }}>{currentGroup.title || 'Unbenanntes Projekt'}</h2>
{/* Jahr und Name */} {/* Jahr und Name */}
<Typography sx={yearAuthorTextSx}>{currentGroup.year}{currentGroup.name && `${currentGroup.name}`}</Typography> <p style={{ color: '#FFD700', fontSize: '18px', fontWeight: 400, marginBottom: '8px', marginTop: 0, fontFamily: 'Open Sans, sans-serif' }}>{currentGroup.year}{currentGroup.name && `${currentGroup.name}`}</p>
{/* Beschreibung (wenn vorhanden) */} {/* Beschreibung (wenn vorhanden) */}
{currentGroup.description && <Typography sx={descriptionTextSx}>{currentGroup.description}</Typography>} {currentGroup.description && <p style={{ color: '#E0E0E0', fontSize: '16px', fontWeight: 300, marginBottom: '8px', marginTop: 0, fontFamily: 'Open Sans, sans-serif', lineHeight: 1.4 }}>{currentGroup.description}</p>}
{/* Meta-Informationen */} {/* Meta-Informationen */}
<Typography sx={metaTextSx}>Bild {currentImageIndex + 1} von {currentGroup.images.length} Slideshow {currentGroupIndex + 1} von {allGroups.length}</Typography> <p style={{ color: '#999', fontSize: '12px', marginTop: '8px', marginBottom: 0, fontFamily: 'Open Sans, sans-serif' }}>Bild {currentImageIndex + 1} von {currentGroup.images.length} Slideshow {currentGroupIndex + 1} von {allGroups.length}</p>
</Box> </div>
</Box> </div>
); );
} }