Project-Image-Uploader/frontend/src/Components/ComponentUtils/MultiUpload/DescriptionInput.js
matthias.lotz 4b9feec887 Refactor: Create modular component architecture for ManagementPortalPage
- Created new modular components:
  * ConsentManager: Manages workshop + social media consents with individual save
  * GroupMetadataEditor: Manages group metadata (title, description, name, year) with save
  * ImageDescriptionManager: Manages image descriptions with batch save
  * DeleteGroupButton: Standalone group deletion component

- Refactored ManagementPortalPage to use modular components:
  * Each component in Paper box with heading inside (not outside)
  * HTML buttons with CSS classes (btn btn-success, btn btn-secondary)
  * Inline feedback with Material-UI Alert instead of SweetAlert2 popups
  * Icons: 💾 save, ↩ discard, 🗑️ delete
  * Individual save/discard functionality per component

- Enhanced ConsentCheckboxes component:
  * Added children prop for flexible composition
  * Conditional heading for manage mode inside Paper box

- Fixed DescriptionInput:
  * Removed duplicate heading (now only in parent component)

- React state management improvements:
  * Deep copy pattern for nested objects/arrays
  * Sorted array comparison for order-insensitive change detection
  * Set-based comparison for detecting removed items
  * Initialization guard to prevent useEffect overwrites

- Bug fixes:
  * Fixed image reordering using existing /api/groups/:groupId/reorder route
  * Fixed edit mode toggle with unsaved changes warning
  * Fixed consent state updates with proper object references
  * Fixed uploadImageBatch signature to use object destructuring
  * Removed unnecessary /api/manage/:token/reorder route from backend

Next: Apply same modular pattern to MultiUploadPage and ModerationGroupImagesPage
2025-11-15 17:25:51 +01:00

143 lines
3.9 KiB
JavaScript

import React from 'react';
import { TextField, Typography, Grid, Box } from '@mui/material';
function DescriptionInput({
metadata = {},
onMetadataChange
}) {
const handleFieldChange = (field, value) => {
const updatedMetadata = {
...metadata,
[field]: value
};
onMetadataChange(updatedMetadata);
};
const currentYear = new Date().getFullYear();
const fieldLabelSx = {
fontFamily: 'roboto',
fontSize: '14px',
color: '#555555',
marginBottom: '8px',
display: 'block'
};
const sectionTitleSx = {
fontFamily: 'roboto',
fontSize: '18px',
color: '#333333',
marginBottom: '15px',
display: 'block',
fontWeight: 500
};
const textFieldSx = {
width: '100%',
marginBottom: '15px',
'& .MuiOutlinedInput-root': {
borderRadius: '8px'
}
};
const requiredFieldSx = {
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
'& fieldset': {
borderColor: '#E57373'
}
}
};
const optionalFieldSx = {
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
'& fieldset': {
borderColor: '#E0E0E0'
}
}
};
const characterCountSx = {
fontSize: '12px',
color: '#999999',
textAlign: 'right',
marginTop: '-10px',
marginBottom: '10px'
};
const requiredIndicatorSx = { color: '#E57373', fontSize: '16px' };
const optionalIndicatorSx = { color: '#9E9E9E', fontSize: '12px', fontStyle: 'italic' };
return (
<Box sx={{ marginTop: '20px', marginBottom: '20px' }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<Typography sx={fieldLabelSx}>
Jahr <Box component="span" sx={requiredIndicatorSx}>*</Box>
</Typography>
<TextField
sx={{ ...textFieldSx, ...requiredFieldSx }}
variant="outlined"
type="number"
value={metadata.year || currentYear}
onChange={(e) => handleFieldChange('year', parseInt(e.target.value))}
placeholder={currentYear.toString()}
inputProps={{
min: 1900,
max: currentYear + 10
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Typography sx={fieldLabelSx}>
Titel <Box component="span" sx={requiredIndicatorSx}>*</Box>
</Typography>
<TextField
sx={{ ...textFieldSx, ...requiredFieldSx }}
variant="outlined"
value={metadata.title || ''}
onChange={(e) => handleFieldChange('title', e.target.value)}
placeholder="z.B. Wohnzimmer Renovierung"
inputProps={{ maxLength: 100 }}
/>
</Grid>
<Grid item xs={12}>
<Typography sx={fieldLabelSx}>
Beschreibung <Box component="span" sx={optionalIndicatorSx}>(optional)</Box>
</Typography>
<TextField
sx={{ ...textFieldSx, ...optionalFieldSx }}
multiline
rows={3}
variant="outlined"
value={metadata.description || ''}
onChange={(e) => handleFieldChange('description', e.target.value)}
placeholder="Detaillierte Beschreibung des Projekts..."
inputProps={{ maxLength: 500 }}
/>
<Box sx={characterCountSx}>{(metadata.description || '').length} / 500 Zeichen</Box>
</Grid>
<Grid item xs={12}>
<Typography sx={fieldLabelSx}>
Name/Ersteller <Box component="span" sx={optionalIndicatorSx}>(optional)</Box>
</Typography>
<TextField
sx={{ ...textFieldSx, ...optionalFieldSx }}
variant="outlined"
value={metadata.name || ''}
onChange={(e) => handleFieldChange('name', e.target.value)}
placeholder="Dein Name oder Projektersteller"
inputProps={{ maxLength: 50 }}
/>
</Grid>
</Grid>
</Box>
);
}
export default DescriptionInput;