feat(F-063): completed feature

This commit is contained in:
chattie
2026-08-19 17:07:45 +02:00
parent 52e30c39d7
commit c34241134f
15 changed files with 227 additions and 22 deletions

View File

@@ -78,13 +78,28 @@ export function ProductEditor({ productId }: ProductEditorProps) {
const snapRef = useRef('');
const dirtyRef = useRef(false);
/**
* Returns a stable JSON snapshot of the form. The function is a `useCallback`
* so the dirty-check effect can safely depend on its identity without
* causing a feedback loop with the load effect. Previously `getSnap` was
* also in the load effect's dependency list, which (combined with React
* state updates inside the load handler) caused the load effect to re-fire
* on every render — a fetch loop that broke checkbox selection.
*/
const getSnap = useCallback(() => JSON.stringify({
name, slug, desc, brandId, categoryIds, channels, featured, attributes, state, seoTitle, seoDesc,
}), [name, slug, desc, brandId, categoryIds, channels, featured, attributes, state, seoTitle, seoDesc]);
// Keep a ref to the latest getSnap so the load effect (which uses an empty
// deps list to avoid the loop) can still compute the initial snapshot.
const getSnapRef = useRef(getSnap);
useEffect(() => { getSnapRef.current = getSnap; }, [getSnap]);
useEffect(() => {
if (!productId) { setLoading(false); return; }
let cancelled = false;
productsApi.get(productId).then((p: Product) => {
if (cancelled) return;
setName(p.name); setSlug(p.slug); setDesc(p.description ?? '');
setBrandId(p.brandId ?? ''); setCategoryIds(p.categoryIds ?? []);
setChannels((p as any).channels ?? 'all');
@@ -93,10 +108,15 @@ export function ProductEditor({ productId }: ProductEditorProps) {
setState(p.state);
setSeoTitle((p as any).seoTitle ?? ''); setSeoTitleManual(true);
setSeoDesc((p as any).seoDescription ?? ''); setSeoDescManual(true);
snapRef.current = getSnap();
snapRef.current = getSnapRef.current();
setLoading(false);
}).catch(() => { setError('No se pudo cargar el producto'); setLoading(false); });
}, [productId, getSnap]);
}).catch(() => {
if (cancelled) return;
setError('No se pudo cargar el producto'); setLoading(false);
});
return () => { cancelled = true; };
// eslint-disable-next-line react-hooks/exhaustive-deps -- load effect runs once per productId
}, [productId]);
useEffect(() => {
if (loading) return;

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB