Adds the Offerings module and retargets the CI/CD pipeline to Api.SlpSoftware
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
This commit is contained in:
2026-08-02 16:23:09 +02:00
co-authored by Claude Sonnet 5
parent b6e9c07c06
commit cfb06b28b6
79 changed files with 4069 additions and 94 deletions
@@ -0,0 +1,53 @@
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 };
}