Project-Image-Uploader/frontend/src/Components/ComponentUtils/ImageGallery.js
matthias.lotz 292d25f5b4 feat: Implement image descriptions - Backend & Core Frontend
- Database: Add image_description column to images table
- Repository: Add updateImageDescription & updateBatchImageDescriptions methods
- API: Add PATCH endpoints for single and batch description updates
- Upload: Support descriptions in batch upload
- Frontend: ImageGalleryCard with Edit mode and textarea
- Frontend: MultiUploadPage with description input
- Frontend: ModerationGroupImagesPage with description editing
- CSS: Styles for edit mode, textarea, and character counter

Phase 1-4 complete: Backend + Core Frontend + Upload + Moderation
2025-11-07 18:34:16 +01:00

160 lines
4.5 KiB
JavaScript

import React from 'react';
import PropTypes from 'prop-types';
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors
} from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
rectSortingStrategy
} from '@dnd-kit/sortable';
import ImageGalleryCard from './ImageGalleryCard';
import './Css/ImageGallery.css';
const ImageGallery = ({
items,
onApprove,
onViewImages,
onDelete,
isPending,
showActions,
mode,
title,
emptyMessage = 'Keine Elemente vorhanden',
enableReordering = false,
onReorder = null,
isEditMode = false,
onEditMode = null,
imageDescriptions = {},
onDescriptionChange = null
}) => {
// Sensors for drag and drop (touch-friendly)
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8, // Require 8px movement before drag starts
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const handleDragEnd = (event) => {
const { active, over } = event;
if (active.id !== over?.id && onReorder) {
const oldIndex = items.findIndex(item =>
(item.id || item.groupId) === active.id
);
const newIndex = items.findIndex(item =>
(item.id || item.groupId) === over.id
);
if (oldIndex !== -1 && newIndex !== -1) {
const reorderedItems = arrayMove(items, oldIndex, newIndex);
onReorder(reorderedItems, oldIndex, newIndex);
}
}
};
if (!items || items.length === 0) {
return (
<div className="image-gallery-empty">
<p>{emptyMessage}</p>
</div>
);
}
const itemIds = items.map(item => item.id || item.groupId);
const galleryContent = (
<div className="image-gallery-grid">
{items.map((item, index) => (
<ImageGalleryCard
key={item.id || item.groupId || index}
item={item}
index={index}
onApprove={onApprove}
onViewImages={onViewImages}
onDelete={onDelete}
isPending={isPending}
showActions={showActions}
mode={mode}
enableReordering={enableReordering}
isEditMode={isEditMode}
onEditMode={onEditMode}
imageDescription={imageDescriptions[item.id || item.groupId] || ''}
onDescriptionChange={onDescriptionChange}
/>
))}
</div>
);
return (
<div className="image-gallery-container">
{title && (
<h2 className="image-gallery-title">{title}</h2>
)}
{enableReordering ? (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={itemIds}
strategy={rectSortingStrategy}
>
{galleryContent}
</SortableContext>
</DndContext>
) : (
galleryContent
)}
</div>
);
};
ImageGallery.propTypes = {
items: PropTypes.array.isRequired,
onApprove: PropTypes.func,
onViewImages: PropTypes.func,
onDelete: PropTypes.func,
isPending: PropTypes.bool,
showActions: PropTypes.bool,
mode: PropTypes.oneOf(['group', 'moderation', 'preview', 'single-image']),
title: PropTypes.string,
emptyMessage: PropTypes.string,
enableReordering: PropTypes.bool,
onReorder: PropTypes.func,
isEditMode: PropTypes.bool,
onEditMode: PropTypes.func,
imageDescriptions: PropTypes.object,
onDescriptionChange: PropTypes.func
};
ImageGallery.defaultProps = {
onApprove: () => {},
onViewImages: () => {},
onDelete: () => {},
isPending: false,
showActions: true,
mode: 'group',
enableReordering: false,
onReorder: null,
isEditMode: false,
onEditMode: null,
imageDescriptions: {},
onDescriptionChange: null
};
export default ImageGallery;