mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 17:53:12 +00:00
156 lines
4.4 KiB
TypeScript
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];
|
|
}
|