import type {
|
ConfigurationEntry,
|
ConfigurationReference,
|
CustomerNumber,
|
} from "@dh-software/baukasten-types";
|
|
/** Stable identity for a reference (customer + key, key carries the subfolder path). */
|
export function referenceKey(reference: { customerNumber: CustomerNumber; key: string }): string {
|
return `${reference.customerNumber} ${reference.key}`;
|
}
|
|
/** A right-side row: a reference resolved against the available catalog for display. */
|
export interface SelectedRow {
|
reference: ConfigurationReference;
|
displayName: string;
|
/** false when the ref is no longer in `available` (permission lost / file removed). */
|
isAvailable: boolean;
|
}
|
|
/** Available entries not yet chosen, in catalog order (the left list). */
|
export function availableEntries(
|
available: ConfigurationEntry[],
|
selected: ConfigurationReference[],
|
): ConfigurationEntry[] {
|
const chosen = new Set(selected.map(referenceKey));
|
return available.filter((entry) => !chosen.has(referenceKey(entry)));
|
}
|
|
/** Chosen references resolved to display rows, top -> bottom (the right list). */
|
export function selectedRows(
|
available: ConfigurationEntry[],
|
selected: ConfigurationReference[],
|
): SelectedRow[] {
|
const byKey = new Map(available.map((entry) => [referenceKey(entry), entry]));
|
return selected.map((reference) => {
|
const match = byKey.get(referenceKey(reference));
|
return {
|
reference,
|
displayName: match ? match.displayName : reference.key,
|
isAvailable: match !== undefined,
|
};
|
});
|
}
|
|
/** Appends entries (as references) that are not already chosen, preserving their order. */
|
export function addReferences(
|
selected: ConfigurationReference[],
|
entries: ReadonlyArray<{ customerNumber: CustomerNumber; key: string }>,
|
): ConfigurationReference[] {
|
const present = new Set(selected.map(referenceKey));
|
const additions = entries
|
.map((entry) => ({ customerNumber: entry.customerNumber, key: entry.key }))
|
.filter((reference) => !present.has(referenceKey(reference)));
|
return additions.length === 0 ? selected : [...selected, ...additions];
|
}
|
|
/** Removes all chosen references whose key is in `keys`. */
|
export function removeByKeys(
|
selected: ConfigurationReference[],
|
keys: ReadonlySet<string>,
|
): ConfigurationReference[] {
|
return selected.filter((reference) => !keys.has(referenceKey(reference)));
|
}
|
|
/** Moves the item at `index` by `delta` (clamped); returns a new array. */
|
export function moveByOffset(
|
selected: ConfigurationReference[],
|
index: number,
|
delta: number,
|
): ConfigurationReference[] {
|
const target = index + delta;
|
if (index < 0 || index >= selected.length || target < 0 || target >= selected.length) {
|
return selected;
|
}
|
const next = selected.slice();
|
const [moved] = next.splice(index, 1);
|
next.splice(target, 0, moved);
|
return next;
|
}
|
|
/** Drag-and-drop reorder: move `fromIndex` to sit at the drop target `toIndex`. */
|
export function reorder(
|
selected: ConfigurationReference[],
|
fromIndex: number,
|
toIndex: number,
|
): ConfigurationReference[] {
|
if (
|
fromIndex === toIndex ||
|
fromIndex < 0 ||
|
fromIndex >= selected.length ||
|
toIndex < 0 ||
|
toIndex >= selected.length
|
) {
|
return selected;
|
}
|
const next = selected.slice();
|
const [moved] = next.splice(fromIndex, 1);
|
const target = fromIndex < toIndex ? toIndex - 1 : toIndex;
|
next.splice(target, 0, moved);
|
return next;
|
}
|