Warnings fixed: - 3.1: _compute_display_name stale-data bug on all-names-clear - 3.3: Location getValue unsafe type cast replaced with typed helper - 3.5: Explicit updated_at timestamp refresh in locations router - 3.6: Drop deprecated relationship column (migration 021, model, schema, TS type) Suggestions fixed: - 4.1: CategoryAutocomplete keyboard navigation (ArrowUp/Down, Enter, Escape) - 4.2: Mobile detail panel backdrop click-to-close on both pages - 4.3: PersonCreate whitespace bypass in require_some_name validator - 4.5/4.6: Extract SortIcon, DataRow, SectionHeader from EntityTable render body - 4.8: PersonForm sends null instead of empty string for birthday - 4.10: Remove unnecessary executeDelete wrapper in EntityDetailPanel Also includes previously completed fixes from prior session: - 2.1: Remove Z suffix from naive timestamp in formatUpdatedAt - 3.2: Drag-then-click conflict prevention in SortableCategoryChip - 3.4: localStorage JSON shape validation in useCategoryOrder - 4.4: Category chip styling consistency (both pages use inline hsl styles) - 4.9: restrictToHorizontalAxis modifier on CategoryFilterBar drag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
42 lines
1.2 KiB
TypeScript
42 lines
1.2 KiB
TypeScript
import { useState, useCallback, useMemo } from 'react';
|
|
|
|
const STORAGE_PREFIX = 'umbra-';
|
|
|
|
export function useCategoryOrder(key: string, categories: string[]) {
|
|
const storageKey = `${STORAGE_PREFIX}${key}-category-order`;
|
|
|
|
const [savedOrder, setSavedOrder] = useState<string[]>(() => {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey);
|
|
if (!raw) return [];
|
|
const parsed = JSON.parse(raw);
|
|
return Array.isArray(parsed) ? parsed.filter((x): x is string => typeof x === 'string') : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
const orderedCategories = useMemo(() => {
|
|
const catSet = new Set(categories);
|
|
// Keep saved items that still exist, in saved order
|
|
const ordered = savedOrder.filter((c) => catSet.has(c));
|
|
// Append any new categories not in saved order
|
|
const remaining = categories.filter((c) => !savedOrder.includes(c));
|
|
return [...ordered, ...remaining];
|
|
}, [categories, savedOrder]);
|
|
|
|
const reorder = useCallback(
|
|
(newOrder: string[]) => {
|
|
setSavedOrder(newOrder);
|
|
try {
|
|
localStorage.setItem(storageKey, JSON.stringify(newOrder));
|
|
} catch {
|
|
// localStorage full — silently ignore
|
|
}
|
|
},
|
|
[storageKey],
|
|
);
|
|
|
|
return { orderedCategories, reorder };
|
|
}
|