feat(F-082): completed feature
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { productsApi, inventoryApi } from '@/lib/api-client';
|
||||
import type { Product, ProductVariant, StockAvailability } from '@/types';
|
||||
import type { ProductVariant, StockAvailability } from '@/types';
|
||||
|
||||
interface VariantRow {
|
||||
productId: string;
|
||||
@@ -140,6 +140,56 @@ export default function InventoryPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Save Stock inline (used by Stock cell on blur/Enter)
|
||||
const saveStockInline = async (variantId: string, value: string) => {
|
||||
const qty = parseInt(value, 10);
|
||||
if (isNaN(qty) || qty < 0) {
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === variantId
|
||||
? { ...r, editing: false, editValue: String(r.stock?.availableQuantity ?? 0), msg: 'Error' }
|
||||
: r,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
setRows((prev) => prev.map((r) => (r.variant.id === variantId ? { ...r, saving: true } : r)));
|
||||
try {
|
||||
const result = await inventoryApi.setStock(variantId, qty);
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === variantId
|
||||
? {
|
||||
...r,
|
||||
stock: { available: result.available > 0, availableQuantity: result.available },
|
||||
editing: false,
|
||||
saving: false,
|
||||
editValue: String(result.available),
|
||||
msg: '✓ Guardado',
|
||||
}
|
||||
: r,
|
||||
),
|
||||
);
|
||||
setTimeout(() => {
|
||||
setRows((prev) => prev.map((r) => (r.variant.id === variantId ? { ...r, msg: '' } : r)));
|
||||
}, 3000);
|
||||
} catch {
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === variantId
|
||||
? {
|
||||
...r,
|
||||
saving: false,
|
||||
editing: false,
|
||||
editValue: String(r.stock?.availableQuantity ?? 0),
|
||||
msg: 'Error',
|
||||
}
|
||||
: r,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Filter rows
|
||||
const filtered = rows.filter((r) => {
|
||||
if (filter === 'in_stock') return (r.stock?.availableQuantity ?? 0) >= 5;
|
||||
@@ -302,8 +352,9 @@ export default function InventoryPage() {
|
||||
{row.loading ? (
|
||||
<span className="text-gray-300">—</span>
|
||||
) : row.editing ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
autoFocus
|
||||
type="number"
|
||||
min={0}
|
||||
value={row.editValue}
|
||||
@@ -316,78 +367,41 @@ export default function InventoryPage() {
|
||||
),
|
||||
)
|
||||
}
|
||||
className="w-20 px-2 py-1 border border-gray-300 rounded-lg text-sm focus:ring-1 focus:ring-[#2D6A4F] outline-none"
|
||||
/>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const qty = parseInt(row.editValue, 10);
|
||||
if (isNaN(qty) || qty < 0) return;
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === row.variant.id ? { ...r, saving: true } : r,
|
||||
),
|
||||
);
|
||||
try {
|
||||
const result = await inventoryApi.setStock(row.variant.id, qty);
|
||||
onBlur={() => saveStockInline(row.variant.id, row.editValue)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') saveStockInline(row.variant.id, row.editValue);
|
||||
if (e.key === 'Escape')
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === row.variant.id
|
||||
? {
|
||||
...r,
|
||||
stock: { available: result.available > 0, availableQuantity: result.available },
|
||||
editing: false,
|
||||
saving: false,
|
||||
msg: '✓',
|
||||
editValue: String(r.stock?.availableQuantity ?? 0),
|
||||
}
|
||||
: r,
|
||||
),
|
||||
);
|
||||
setTimeout(() => {
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === row.variant.id ? { ...r, msg: '' } : r,
|
||||
),
|
||||
);
|
||||
}, 3000);
|
||||
} catch {
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === row.variant.id
|
||||
? { ...r, saving: false, msg: 'Error' }
|
||||
: r,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
disabled={row.saving}
|
||||
className="px-2 py-1 bg-[#2D6A4F] text-white text-xs rounded-lg hover:bg-[#1B4332] disabled:opacity-50"
|
||||
>
|
||||
{row.saving ? '...' : 'OK'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === row.variant.id
|
||||
? {
|
||||
...r,
|
||||
editing: false,
|
||||
editValue: String(r.stock?.availableQuantity ?? 0),
|
||||
}
|
||||
: r,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="text-gray-400 hover:text-gray-600 text-xs"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
className="w-20 px-2 py-1 border border-gray-300 rounded-lg text-sm focus:ring-1 focus:ring-[#2D6A4F] outline-none"
|
||||
/>
|
||||
{row.msg && (
|
||||
<span className={`text-xs ${row.msg === '✓' || row.msg === '✓ Guardado' ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{row.msg}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() =>
|
||||
setRows(prev =>
|
||||
prev.map(r => r.variant.id === row.variant.id ? { ...r, editing: true } : r))
|
||||
setRows((prev) =>
|
||||
prev.map((r) =>
|
||||
r.variant.id === row.variant.id
|
||||
? { ...r, editing: true, editValue: String(r.stock?.availableQuantity ?? 0) }
|
||||
: r,
|
||||
),
|
||||
)
|
||||
}
|
||||
title="Clic para editar stock"
|
||||
className="font-medium text-gray-900 hover:text-[#2D6A4F] cursor-text disabled:opacity-50"
|
||||
|
||||
Reference in New Issue
Block a user