UMBRA/frontend/src/hooks/useCategoryOrder.ts
Kyle Pope 1806e15487 Address all QA review warnings and suggestions for entity pages
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>
2026-02-25 01:04:20 +08:00

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 };
}