Continuous Integration / config (pull_request) Successful in 12s
Continuous Integration / changes (pull_request) Successful in 22s
Continuous Integration / backend-build (pull_request) Successful in 5m53s
Continuous Integration / vulnerability-scan (pull_request) Successful in 5m46s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m54s
Continuous Integration / backend-test (pull_request) Successful in 7m37s
Continuous Integration / frontend-build (pull_request) Successful in 2m14s
Continuous Integration / frontend-test (pull_request) Successful in 4m59s
Continuous Integration / frontend-lint (pull_request) Successful in 2m2s
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Continuous Integration / publish-test (pull_request) Successful in 7m34s
Continuous Integration / deploy-test (pull_request) Skipped
Implements Unit 2 "Offerings" (backend module, admin CRUD UI with drag-and-drop reordering, public GET /api/v1/offerings endpoint) and executes the feature's D-15 CI/CD cutover, switching the deploy pipeline's build/publish target from SlpModularCms.Api to SlpModularCms.Api.SlpSoftware. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FWyStNL2ZsjrS7FLd7xvvN
54 lines
2.0 KiB
TypeScript
54 lines
2.0 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
PointerSensor,
|
|
KeyboardSensor,
|
|
useSensor,
|
|
useSensors,
|
|
type DragStartEvent,
|
|
type DragEndEvent,
|
|
} from '@dnd-kit/core';
|
|
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
import { useReorderOfferings } from '../services/useReorderOfferings';
|
|
import type { OfferingAdminDto } from '../services/types';
|
|
|
|
export function useOfferingsDnd(offerings: OfferingAdminDto[]) {
|
|
const [items, setItems] = useState(offerings);
|
|
const [syncedOfferings, setSyncedOfferings] = useState(offerings);
|
|
const [activeId, setActiveId] = useState<string | null>(null);
|
|
const reorderMutation = useReorderOfferings();
|
|
|
|
// Adjusting state during render (not in an effect) avoids the extra render pass
|
|
// an effect-based sync would cause — see https://react.dev/learn/you-might-not-need-an-effect.
|
|
if (offerings !== syncedOfferings) {
|
|
setSyncedOfferings(offerings);
|
|
setItems(offerings);
|
|
}
|
|
|
|
const sensors = useSensors(
|
|
useSensor(PointerSensor),
|
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
|
);
|
|
|
|
function handleDragStart(event: DragStartEvent) {
|
|
setActiveId(event.active.id as string);
|
|
}
|
|
|
|
function handleDragEnd(event: DragEndEvent) {
|
|
setActiveId(null);
|
|
const { active, over } = event;
|
|
if (over === null || active.id === over.id) return;
|
|
|
|
const oldIndex = items.findIndex((item) => item.id === active.id);
|
|
const newIndex = items.findIndex((item) => item.id === over.id);
|
|
if (oldIndex === -1 || newIndex === -1) return;
|
|
|
|
const reordered = arrayMove(items, oldIndex, newIndex);
|
|
setItems(reordered);
|
|
reorderMutation.mutate(reordered.map((item) => item.id));
|
|
}
|
|
|
|
const activeItem = activeId === null ? null : (items.find((item) => item.id === activeId) ?? null);
|
|
|
|
return { items, sensors, activeItem, handleDragStart, handleDragEnd };
|
|
}
|