feat(F-086): completed feature
This commit is contained in:
@@ -64,6 +64,7 @@ export function ProductEditor({ productId }: ProductEditorProps) {
|
||||
const [seoTitleManual, setSeoTitleManual] = useState(false);
|
||||
const [seoDesc, setSeoDesc] = useState('');
|
||||
const [seoDescManual, setSeoDescManual] = useState(false);
|
||||
const [expirationDate, setExpirationDate] = useState('');
|
||||
const [brands, setBrands] = useState<Brand[]>([]);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
|
||||
@@ -88,8 +89,8 @@ export function ProductEditor({ productId }: ProductEditorProps) {
|
||||
* 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]);
|
||||
name, slug, desc, brandId, categoryIds, channels, featured, attributes, state, seoTitle, seoDesc, expirationDate,
|
||||
}), [name, slug, desc, brandId, categoryIds, channels, featured, attributes, state, seoTitle, seoDesc, expirationDate]);
|
||||
|
||||
// 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.
|
||||
@@ -109,6 +110,7 @@ export function ProductEditor({ productId }: ProductEditorProps) {
|
||||
setState(p.state);
|
||||
setSeoTitle((p as any).seoTitle ?? ''); setSeoTitleManual(true);
|
||||
setSeoDesc((p as any).seoDescription ?? ''); setSeoDescManual(true);
|
||||
setExpirationDate((p as any).expirationDate ?? '');
|
||||
snapRef.current = getSnapRef.current();
|
||||
setLoading(false);
|
||||
}).catch(() => {
|
||||
@@ -122,7 +124,7 @@ export function ProductEditor({ productId }: ProductEditorProps) {
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
dirtyRef.current = getSnap() !== snapRef.current;
|
||||
}, [name, slug, desc, brandId, categoryIds, channels, featured, attributes, state, seoTitle, seoDesc, loading, getSnap]);
|
||||
}, [name, slug, desc, brandId, categoryIds, channels, featured, attributes, state, seoTitle, seoDesc, expirationDate, loading, getSnap]);
|
||||
|
||||
useEffect(() => {
|
||||
const h = (e: BeforeUnloadEvent) => { if (dirtyRef.current) { e.preventDefault(); e.returnValue = ''; } };
|
||||
@@ -151,6 +153,7 @@ export function ProductEditor({ productId }: ProductEditorProps) {
|
||||
state,
|
||||
seoTitle: seoTitle || undefined,
|
||||
seoDescription: seoDesc || undefined,
|
||||
expirationDate: expirationDate || undefined,
|
||||
};
|
||||
let saved: Product;
|
||||
if (isCreate) saved = await productsApi.create(payload);
|
||||
@@ -385,6 +388,21 @@ export function ProductEditor({ productId }: ProductEditorProps) {
|
||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-[#2D6A4F] outline-none resize-none" />
|
||||
<div className="mt-1 text-xs text-gray-400">{seoDesc.length}/160</div>
|
||||
</div>
|
||||
|
||||
{/* Fecha de caducidad */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<label className="text-sm font-semibold text-gray-900">Fecha de caducidad</label>
|
||||
<span className="text-xs text-gray-400">opcional</span>
|
||||
</div>
|
||||
<input
|
||||
type="date"
|
||||
value={expirationDate}
|
||||
onChange={(e) => setExpirationDate(e.target.value)}
|
||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-[#2D6A4F] outline-none"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-400">Se mostrará en el listado de productos y en la tienda.</p>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user