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 { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Button, Container } from '@mui/material';
|
import { Button, Container } from '@mui/material';
|
||||||
import Swal from 'sweetalert2/dist/sweetalert2.js';
|
import Swal from 'sweetalert2/dist/sweetalert2.js';
|
||||||
|
|
@ -10,6 +10,9 @@ import Footer from '../ComponentUtils/Footer';
|
||||||
import ImageGallery from '../ComponentUtils/ImageGallery';
|
import ImageGallery from '../ComponentUtils/ImageGallery';
|
||||||
import DescriptionInput from '../ComponentUtils/MultiUpload/DescriptionInput';
|
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
|
// selectedImages will hold objects compatible with ImagePreviewGallery
|
||||||
const [selectedImages, setSelectedImages] = useState([]);
|
const [selectedImages, setSelectedImages] = useState([]);
|
||||||
const [metadata, setMetadata] = useState({ year: new Date().getFullYear(), title: '', description: '', name: '' });
|
const [metadata, setMetadata] = useState({ year: new Date().getFullYear(), title: '', description: '', name: '' });
|
||||||
|
const [isReordering, setIsReordering] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadGroup();
|
loadGroup();
|
||||||
|
|
@ -122,6 +126,53 @@ const ModerationGroupImagesPage = () => {
|
||||||
setSelectedImages(prev => prev.filter((_, index) => index !== indexToRemove));
|
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
|
// Note: approve/delete group actions are intentionally removed from this page
|
||||||
|
|
||||||
if (loading) return <div className="moderation-loading">Lade Gruppe...</div>;
|
if (loading) return <div className="moderation-loading">Lade Gruppe...</div>;
|
||||||
|
|
@ -136,6 +187,9 @@ const ModerationGroupImagesPage = () => {
|
||||||
<ImageGallery
|
<ImageGallery
|
||||||
items={selectedImages}
|
items={selectedImages}
|
||||||
onDelete={handleRemoveImage}
|
onDelete={handleRemoveImage}
|
||||||
|
onReorder={handleReorder}
|
||||||
|
enableReordering={true}
|
||||||
|
isReordering={isReordering}
|
||||||
mode="preview"
|
mode="preview"
|
||||||
showActions={true}
|
showActions={true}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ const PublicGroupImagesPage = () => {
|
||||||
id: img.id
|
id: img.id
|
||||||
})) : []}
|
})) : []}
|
||||||
showActions={false}
|
showActions={false}
|
||||||
|
enableReordering={false}
|
||||||
mode="single-image"
|
mode="single-image"
|
||||||
emptyMessage="Keine Bilder in dieser Gruppe."
|
emptyMessage="Keine Bilder in dieser Gruppe."
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,37 @@
|
||||||
import { sendRequest } from './sendRequest';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service für Drag-and-Drop Reordering von Bildern
|
* Service für Drag-and-Drop Reordering von Bildern
|
||||||
*/
|
*/
|
||||||
class ReorderService {
|
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
|
* Reorder images within a group
|
||||||
* @param {string} groupId - The group ID
|
* @param {string} groupId - The group ID
|
||||||
|
|
@ -21,7 +48,7 @@ class ReorderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await sendRequest(`/api/groups/${groupId}/reorder`, 'PUT', {
|
const response = await this.makeRequest(`/api/groups/${groupId}/reorder`, 'PUT', {
|
||||||
imageIds: imageIds
|
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