From f6feb592d2468ef1ff6b801fd4716ad6352f6f52 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Tue, 3 Feb 2026 23:42:59 +0300 Subject: [PATCH] Add deferred save for prize reordering in admin - Remove auto-save on drag-and-drop (prevents UI jumps) - Store prize order changes locally - Show warning banner with unsaved changes - Add Save and Cancel buttons - Only trigger API call when user clicks Save - Add translations for unsaved order messages --- src/pages/AdminWheel.tsx | 70 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/src/pages/AdminWheel.tsx b/src/pages/AdminWheel.tsx index 6f0c5e0..461ec81 100644 --- a/src/pages/AdminWheel.tsx +++ b/src/pages/AdminWheel.tsx @@ -277,6 +277,8 @@ export default function AdminWheel() { const [activeTab, setActiveTab] = useState('settings'); const [expandedPrizeId, setExpandedPrizeId] = useState(null); const [isCreating, setIsCreating] = useState(false); + const [localPrizeOrder, setLocalPrizeOrder] = useState(null); + const [hasUnsavedOrder, setHasUnsavedOrder] = useState(false); // Settings form state const [settingsForm, setSettingsForm] = useState<{ @@ -372,6 +374,9 @@ export default function AdminWheel() { mutationFn: adminWheelApi.reorderPrizes, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-wheel-config'] }); + // Reset local state after successful save + setLocalPrizeOrder(null); + setHasUnsavedOrder(false); }, }); @@ -426,13 +431,38 @@ export default function AdminWheel() { const newIndex = config.prizes.findIndex((p) => p.id === over.id); if (oldIndex !== -1 && newIndex !== -1) { const newOrder = arrayMove(config.prizes, oldIndex, newIndex); - reorderPrizesMutation.mutate(newOrder.map((p) => p.id)); + // Save order locally, don't trigger mutation immediately + setLocalPrizeOrder(newOrder.map((p) => p.id)); + setHasUnsavedOrder(true); } } }, - [config, onTelegramDragEnd, reorderPrizesMutation], + [config, onTelegramDragEnd], ); + // Save prize order + const handleSavePrizeOrder = useCallback(() => { + if (localPrizeOrder) { + reorderPrizesMutation.mutate(localPrizeOrder); + setHasUnsavedOrder(false); + } + }, [localPrizeOrder, reorderPrizesMutation]); + + // Discard order changes + const handleDiscardOrderChanges = useCallback(() => { + setLocalPrizeOrder(null); + setHasUnsavedOrder(false); + }, []); + + // Get prizes in display order (use local order if available) + const displayedPrizes = config?.prizes + ? localPrizeOrder + ? localPrizeOrder + .map((id) => config.prizes.find((p) => p.id === id)) + .filter((p): p is WheelPrizeAdmin => p !== undefined) + : config.prizes + : []; + if (isLoading) { return (
@@ -764,6 +794,38 @@ export default function AdminWheel() {
+ {/* Unsaved order changes banner */} + {hasUnsavedOrder && ( +
+
+

+ {t('admin.wheel.prizes.unsavedOrder') || 'Есть несохраненные изменения порядка'} +

+

+ {t('admin.wheel.prizes.unsavedOrderHint') || + 'Сохраните изменения или отмените их'} +

+
+
+ + +
+
+ )} + {/* Create new prize inline form */} {isCreating && ( p.id)} + items={displayedPrizes.map((p) => p.id)} strategy={verticalListSortingStrategy} >
- {config.prizes.map((prize) => ( + {displayedPrizes.map((prize) => (