- Add migrations 019/020: extend Person (first/last name, nickname, is_favourite, company, job_title, mobile, category) and Location (is_frequent, contact_number, email) - Update Person/Location models, schemas, and routers with new fields + name denormalisation - Create shared component library: EntityTable, EntityDetailPanel, CategoryFilterBar, CopyableField, CategoryAutocomplete, useTableVisibility hook - Rebuild LocationsPage: table layout with sortable columns, detail side panel, category filter bar, frequent pinned section - Extend LocationForm with contact number, email, frequent toggle, category autocomplete - Add animated panel transitions to ProjectDetail (55/45 split with cubic-bezier easing) - Update TypeScript interfaces for Person and Location Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
import { useState, useEffect, useRef } from 'react';
|
|
|
|
export type VisibilityMode = 'all' | 'filtered' | 'essential';
|
|
|
|
/**
|
|
* Observes container width via ResizeObserver and returns a visibility mode
|
|
* for table columns. Adjusts thresholds when a side panel is open.
|
|
*/
|
|
export function useTableVisibility(
|
|
containerRef: React.RefObject<HTMLElement>,
|
|
panelOpen: boolean
|
|
): VisibilityMode {
|
|
const [mode, setMode] = useState<VisibilityMode>('all');
|
|
const timerRef = useRef<ReturnType<typeof setTimeout>>();
|
|
|
|
useEffect(() => {
|
|
const el = containerRef.current;
|
|
if (!el) return;
|
|
|
|
const calculate = (width: number): VisibilityMode => {
|
|
if (panelOpen) {
|
|
return width >= 600 ? 'filtered' : 'essential';
|
|
}
|
|
if (width >= 900) return 'all';
|
|
if (width >= 600) return 'filtered';
|
|
return 'essential';
|
|
};
|
|
|
|
const observer = new ResizeObserver((entries) => {
|
|
clearTimeout(timerRef.current);
|
|
timerRef.current = setTimeout(() => {
|
|
const entry = entries[0];
|
|
if (entry) {
|
|
setMode(calculate(entry.contentRect.width));
|
|
}
|
|
}, 50);
|
|
});
|
|
|
|
observer.observe(el);
|
|
// Set initial value
|
|
setMode(calculate(el.getBoundingClientRect().width));
|
|
|
|
return () => {
|
|
observer.disconnect();
|
|
clearTimeout(timerRef.current);
|
|
};
|
|
}, [containerRef, panelOpen]);
|
|
|
|
// Recalculate when panelOpen changes
|
|
useEffect(() => {
|
|
const el = containerRef.current;
|
|
if (!el) return;
|
|
const width = el.getBoundingClientRect().width;
|
|
if (panelOpen) {
|
|
setMode(width >= 600 ? 'filtered' : 'essential');
|
|
} else {
|
|
if (width >= 900) setMode('all');
|
|
else if (width >= 600) setMode('filtered');
|
|
else setMode('essential');
|
|
}
|
|
}, [panelOpen, containerRef]);
|
|
|
|
return mode;
|
|
}
|