feat(F-130): completed feature

This commit is contained in:
chattie
2026-08-21 18:08:14 +02:00
parent 55c3ba2d69
commit 69b882dd48
10 changed files with 306 additions and 10 deletions

View File

@@ -18,6 +18,10 @@ interface ProductRow {
editingEan: boolean;
editEanValue: string;
savingEan: boolean;
// F-130: caducidad editable inline
editingExp: boolean;
editExpValue: string;
savingExp: boolean;
}
type RowFilter = 'all' | 'in_stock' | 'low_stock' | 'out_of_stock' | 'expiring' | 'low_margin';
@@ -122,6 +126,9 @@ export default function InventoryPage() {
editingEan: false,
editEanValue: '',
savingEan: false,
editingExp: false,
editExpValue: product.expirationDate?.slice(0, 10) ?? '',
savingExp: false,
});
}
@@ -176,6 +183,30 @@ export default function InventoryPage() {
}
};
// Save expiration date inline (F-130)
const handleSaveExp = async (variantId: string, productId: string, value: string) => {
setRows((prev) => prev.map((r) => (r.variant.id === variantId ? { ...r, savingExp: true } : r)));
try {
const updated = await productsApi.update(productId, { expirationDate: value || null });
const newIso = updated.expirationDate ?? null;
setRows((prev) =>
prev.map((r) =>
r.variant.id === variantId
? {
...r,
expirationDate: newIso,
editExpValue: newIso?.slice(0, 10) ?? '',
editingExp: false,
savingExp: false,
}
: r,
),
);
} catch {
setRows((prev) => prev.map((r) => (r.variant.id === variantId ? { ...r, savingExp: false } : r)));
}
};
// Save Stock inline (used by Stock cell on blur/Enter)
const saveStockInline = async (variantId: string, value: string) => {
const qty = parseInt(value, 10);
@@ -387,7 +418,48 @@ export default function InventoryPage() {
)}
</td>
<td className="px-4 py-3">
<ExpirationCell dateIso={row.expirationDate} />
{row.editingExp ? (
<input
autoFocus
type="date"
value={row.editExpValue}
onChange={(e) =>
setRows((prev) =>
prev.map((r) =>
r.variant.id === row.variant.id
? { ...r, editExpValue: e.target.value }
: r,
),
)
}
onBlur={() => handleSaveExp(row.variant.id, row.productId, row.editExpValue)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSaveExp(row.variant.id, row.productId, row.editExpValue);
if (e.key === 'Escape')
setRows((prev) =>
prev.map((r) =>
r.variant.id === row.variant.id ? { ...r, editingExp: false } : r,
),
);
}}
disabled={row.savingExp}
className="px-2 py-1 border border-[#2D6A4F] rounded text-xs focus:ring-1 focus:ring-[#2D6A4F] outline-none disabled:opacity-50"
/>
) : (
<button
onClick={() =>
setRows((prev) =>
prev.map((r) =>
r.variant.id === row.variant.id ? { ...r, editingExp: true } : r,
),
)
}
title="Clic para editar caducidad"
className="cursor-pointer text-left"
>
<ExpirationCell dateIso={row.expirationDate} />
</button>
)}
</td>
<td className="px-4 py-3">
{margin === null ? (

File diff suppressed because one or more lines are too long