feat: complete drag-and-drop reordering integration
✅ Phase 2 Complete - Frontend Integration: - Fixed service imports and exports in reorderService.js - Added HTTP request helper to replace missing sendRequest - Integrated reordering in ModerationGroupImagesPage (Admin-only) - Disabled reordering in PublicGroupImagesPage (Public users) - Added optimistic updates with error rollback - Added success/error notifications via SweetAlert2 - Fixed useCallback dependency warnings ✅ Reordering Features: - Drag handles always visible for touch devices - Mobile-friendly drag zones and visual feedback - Loading states during API calls - Automatic slideshow integration via upload_order - Complete error handling and validation Next: End-to-end testing across browsers and devices
This commit is contained in:
parent
7564525c7e
commit
e20b1e433d
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Button, Container } from '@mui/material';
|
||||
import Swal from 'sweetalert2/dist/sweetalert2.js';
|
||||
|
|
@ -10,6 +10,9 @@ import Footer from '../ComponentUtils/Footer';
|
|||
import ImageGallery from '../ComponentUtils/ImageGallery';
|
||||
import DescriptionInput from '../ComponentUtils/MultiUpload/DescriptionInput';
|
||||
|
||||
// Services
|
||||
import { updateImageOrder } from '../../services/reorderService';
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -24,6 +27,7 @@ const ModerationGroupImagesPage = () => {
|
|||
// selectedImages will hold objects compatible with ImagePreviewGallery
|
||||
const [selectedImages, setSelectedImages] = useState([]);
|
||||
const [metadata, setMetadata] = useState({ year: new Date().getFullYear(), title: '', description: '', name: '' });
|
||||
const [isReordering, setIsReordering] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadGroup();
|
||||
|
|
@ -122,6 +126,53 @@ const ModerationGroupImagesPage = () => {
|
|||
setSelectedImages(prev => prev.filter((_, index) => index !== indexToRemove));
|
||||
};
|
||||
|
||||
// Handle drag-and-drop reordering
|
||||
const handleReorder = useCallback(async (reorderedItems) => {
|
||||
if (isReordering) return; // Prevent concurrent reordering
|
||||
|
||||
try {
|
||||
setIsReordering(true);
|
||||
console.log('🔄 Reordering images:', reorderedItems.map(img => ({ id: img.id, fileName: img.fileName })));
|
||||
|
||||
// Update local state immediately (optimistic update)
|
||||
setSelectedImages(reorderedItems);
|
||||
|
||||
// Also update group state to keep consistency
|
||||
if (group) {
|
||||
setGroup({ ...group, images: reorderedItems });
|
||||
}
|
||||
|
||||
// Send API request
|
||||
await updateImageOrder(groupId, reorderedItems.map(img => img.id));
|
||||
|
||||
// Show success feedback
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Reihenfolge gespeichert',
|
||||
timer: 1500,
|
||||
showConfirmButton: false,
|
||||
toast: true,
|
||||
position: 'top-end'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Neuordnen:', error);
|
||||
|
||||
// Rollback on error - reload original order
|
||||
await loadGroup();
|
||||
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Fehler beim Speichern',
|
||||
text: 'Reihenfolge konnte nicht gespeichert werden',
|
||||
timer: 3000,
|
||||
showConfirmButton: false
|
||||
});
|
||||
} finally {
|
||||
setIsReordering(false);
|
||||
}
|
||||
}, [groupId, group, isReordering, loadGroup]);
|
||||
|
||||
// Note: approve/delete group actions are intentionally removed from this page
|
||||
|
||||
if (loading) return <div className="moderation-loading">Lade Gruppe...</div>;
|
||||
|
|
@ -136,6 +187,9 @@ const ModerationGroupImagesPage = () => {
|
|||
<ImageGallery
|
||||
items={selectedImages}
|
||||
onDelete={handleRemoveImage}
|
||||
onReorder={handleReorder}
|
||||
enableReordering={true}
|
||||
isReordering={isReordering}
|
||||
mode="preview"
|
||||
showActions={true}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ const PublicGroupImagesPage = () => {
|
|||
id: img.id
|
||||
})) : []}
|
||||
showActions={false}
|
||||
enableReordering={false}
|
||||
mode="single-image"
|
||||
emptyMessage="Keine Bilder in dieser Gruppe."
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,37 @@
|
|||
import { sendRequest } from './sendRequest';
|
||||
|
||||
/**
|
||||
* Service für Drag-and-Drop Reordering von Bildern
|
||||
*/
|
||||
class ReorderService {
|
||||
|
||||
/**
|
||||
* Internal HTTP request helper
|
||||
* @param {string} url - API endpoint URL
|
||||
* @param {string} method - HTTP method (GET, POST, PUT, DELETE)
|
||||
* @param {Object} data - Request body data
|
||||
* @returns {Promise<Object>} API response
|
||||
*/
|
||||
async makeRequest(url, method = 'GET', data = null) {
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder images within a group
|
||||
* @param {string} groupId - The group ID
|
||||
|
|
@ -21,7 +48,7 @@ class ReorderService {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await sendRequest(`/api/groups/${groupId}/reorder`, 'PUT', {
|
||||
const response = await this.makeRequest(`/api/groups/${groupId}/reorder`, 'PUT', {
|
||||
imageIds: imageIds
|
||||
});
|
||||
|
||||
|
|
@ -79,4 +106,13 @@ class ReorderService {
|
|||
}
|
||||
}
|
||||
|
||||
export default new ReorderService();
|
||||
// Create and export service instance
|
||||
const reorderService = new ReorderService();
|
||||
|
||||
export default reorderService;
|
||||
|
||||
// Named exports for easier testing
|
||||
export const updateImageOrder = reorderService.updateImageOrder.bind(reorderService);
|
||||
export const validateImageIds = reorderService.validateImageIds.bind(reorderService);
|
||||
export const extractImageIds = reorderService.extractImageIds.bind(reorderService);
|
||||
export const reorderArray = reorderService.reorderArray.bind(reorderService);
|
||||
Loading…
Reference in New Issue
Block a user