shoc-frontend-new/src/domain/work-orders/utils/board-row-order-storage.ts

156 lines
4.4 KiB
TypeScript

export type BoardRowOrderByDay = Record<string, string[]>;
const STORAGE_PREFIX = "shoc:wo-board-row-order:v1:";
export function boardRowOrderStorageKey(weekMonday: string): string {
return `${STORAGE_PREFIX}${weekMonday}`;
}
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every((item) => typeof item === "string");
}
function isBoardRowOrderByDay(value: unknown): value is BoardRowOrderByDay {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
return false;
}
return Object.values(value).every(isStringArray);
}
export function loadBoardRowOrder(weekMonday: string): BoardRowOrderByDay {
if (typeof window === "undefined" || !weekMonday) {
return {};
}
try {
const raw = localStorage.getItem(boardRowOrderStorageKey(weekMonday));
if (!raw) {
return {};
}
const parsed: unknown = JSON.parse(raw);
return isBoardRowOrderByDay(parsed) ? parsed : {};
} catch {
return {};
}
}
export function saveBoardRowOrder(weekMonday: string, orderByDay: BoardRowOrderByDay): void {
if (typeof window === "undefined" || !weekMonday) {
return;
}
try {
localStorage.setItem(boardRowOrderStorageKey(weekMonday), JSON.stringify(orderByDay));
} catch {
// Quota exceeded or storage blocked — keep in-memory order only.
}
}
/** Reorder `items` so known ids follow `orderedIds`; unknown ids stay at the end (stable). */
export function applyDayRowOrder<T extends { id: string | number }>(
items: T[],
orderedIds: string[] | undefined,
): T[] {
if (!orderedIds?.length || items.length <= 1) {
return items;
}
const orderMap = new Map(orderedIds.map((id, idx) => [id, idx]));
return [...items].sort((a, b) => {
const ai = orderMap.get(String(a.id));
const bi = orderMap.get(String(b.id));
if (ai === undefined && bi === undefined) {
return 0;
}
if (ai === undefined) {
return 1;
}
if (bi === undefined) {
return -1;
}
return ai - bi;
});
}
/** Apply saved per-day orders across a flat row list (preserves relative order across days). */
export function applyRowOrderByDay<T extends { id: string | number; dayGroup: string }>(
items: T[],
orderByDay: BoardRowOrderByDay,
): T[] {
if (!items.length || !Object.keys(orderByDay).length) {
return items;
}
const byDay = new Map<string, T[]>();
for (const item of items) {
const group = byDay.get(item.dayGroup);
if (group) {
group.push(item);
} else {
byDay.set(item.dayGroup, [item]);
}
}
const orderedByDay = new Map<string, T[]>();
for (const [dayGroup, dayItems] of byDay) {
orderedByDay.set(dayGroup, applyDayRowOrder(dayItems, orderByDay[dayGroup]));
}
const nextIndex = new Map<string, number>();
return items.map((item) => {
const dayItems = orderedByDay.get(item.dayGroup)!;
const idx = nextIndex.get(item.dayGroup) ?? 0;
nextIndex.set(item.dayGroup, idx + 1);
return dayItems[idx]!;
});
}
export function reorderIdsInList(ids: string[], dragId: string, overId: string): string[] | null {
const dragIdx = ids.indexOf(dragId);
const overIdx = ids.indexOf(overId);
if (dragIdx < 0 || overIdx < 0 || dragIdx === overIdx) {
return null;
}
const next = [...ids];
next.splice(dragIdx, 1);
next.splice(overIdx, 0, dragId);
return next;
}
/** Remove an id from every day bucket (used when a row changes dayGroup). */
export function removeIdFromRowOrder(
orderByDay: BoardRowOrderByDay,
id: string,
): BoardRowOrderByDay {
let changed = false;
const next: BoardRowOrderByDay = {};
for (const [dayGroup, ids] of Object.entries(orderByDay)) {
if (!ids.includes(id)) {
next[dayGroup] = ids;
continue;
}
changed = true;
const filtered = ids.filter((item) => item !== id);
if (filtered.length) {
next[dayGroup] = filtered;
}
}
return changed ? next : orderByDay;
}
/**
* Merge a visible day's new order into stored order: visible ids take the new sequence,
* ids that were stored but not currently visible stay after the visible block (preserving
* their prior relative order).
*/
export function mergeDayOrder(
previousDayIds: string[] | undefined,
visibleOrderedIds: string[],
): string[] {
if (!previousDayIds?.length) {
return visibleOrderedIds;
}
const visibleSet = new Set(visibleOrderedIds);
const hidden = previousDayIds.filter((id) => !visibleSet.has(id));
return [...visibleOrderedIds, ...hidden];
}