|
|
@@ -49,6 +49,19 @@ import { |
|
|
validateStockTakeQtyString, |
|
|
validateStockTakeQtyString, |
|
|
} from "@/app/utils/formatUtil"; |
|
|
} from "@/app/utils/formatUtil"; |
|
|
|
|
|
|
|
|
|
|
|
const pickerQtyFieldSx = { |
|
|
|
|
|
width: 168, |
|
|
|
|
|
minWidth: 168, |
|
|
|
|
|
"& .MuiInputBase-root": { minHeight: 48 }, |
|
|
|
|
|
"& .MuiInputBase-input": { |
|
|
|
|
|
fontSize: "1.125rem", |
|
|
|
|
|
height: "1.5em", |
|
|
|
|
|
padding: "12px 10px", |
|
|
|
|
|
}, |
|
|
|
|
|
} as const; |
|
|
|
|
|
|
|
|
|
|
|
const pickerTouchButtonSx = { minHeight: 48, minWidth: 96, px: 2, fontSize: "1rem" }; |
|
|
|
|
|
|
|
|
interface PickerStockTakeProps { |
|
|
interface PickerStockTakeProps { |
|
|
selectedSession: AllPickedStockTakeListReponse; |
|
|
selectedSession: AllPickedStockTakeListReponse; |
|
|
onBack: () => void; |
|
|
onBack: () => void; |
|
|
@@ -262,6 +275,8 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
//await loadDetails(page, pageSize, { silent: true }); |
|
|
//await loadDetails(page, pageSize, { silent: true }); |
|
|
|
|
|
const nextQtyId = editableQtyIdsRef.current[editableQtyIdsRef.current.indexOf(detail.id) + 1]; |
|
|
|
|
|
if (nextQtyId != null) focusQtyInput(nextQtyId); |
|
|
setInventoryLotDetails((prev) => |
|
|
setInventoryLotDetails((prev) => |
|
|
prev.map((d) => |
|
|
prev.map((d) => |
|
|
d.id === detail.id |
|
|
d.id === detail.id |
|
|
@@ -413,6 +428,35 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
e.preventDefault(); |
|
|
e.preventDefault(); |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
const qtyInputRefs = useRef(new Map<number, HTMLInputElement>()); |
|
|
|
|
|
const editableQtyIdsRef = useRef<number[]>([]); |
|
|
|
|
|
|
|
|
|
|
|
const focusQtyInput = (id: number) => { |
|
|
|
|
|
const el = qtyInputRefs.current.get(id); |
|
|
|
|
|
if (!el) return; |
|
|
|
|
|
el.focus(); |
|
|
|
|
|
el.scrollIntoView({ block: "center", inline: "nearest" }); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const onQtyRowClick = (detailId: number) => (e: React.MouseEvent) => { |
|
|
|
|
|
const target = e.target as HTMLElement; |
|
|
|
|
|
if (target.closest("button, input, textarea, a, .MuiInputBase-root")) return; |
|
|
|
|
|
focusQtyInput(detailId); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const focusNextQtyInput = (currentId: number) => { |
|
|
|
|
|
const ids = editableQtyIdsRef.current; |
|
|
|
|
|
const nextId = ids[ids.indexOf(currentId) + 1]; |
|
|
|
|
|
if (nextId == null) return; |
|
|
|
|
|
focusQtyInput(nextId); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const onQtyKeyDown = (detailId: number) => (e: React.KeyboardEvent<HTMLInputElement>) => { |
|
|
|
|
|
blockNonIntegerKeys(e); |
|
|
|
|
|
if (e.key !== "Enter") return; |
|
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
focusNextQtyInput(detailId); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => { |
|
|
const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => { |
|
|
if (selectedSession?.status?.toLowerCase() === "completed") { |
|
|
if (selectedSession?.status?.toLowerCase() === "completed") { |
|
|
@@ -424,6 +468,9 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
} |
|
|
} |
|
|
return false; |
|
|
return false; |
|
|
}, [selectedSession?.status]); |
|
|
}, [selectedSession?.status]); |
|
|
|
|
|
editableQtyIdsRef.current = inventoryLotDetails |
|
|
|
|
|
.filter((detail) => !isSubmitDisabled(detail) && (detail.firstStockTakeQty == null || detail.secondStockTakeQty == null)) |
|
|
|
|
|
.map((detail) => detail.id); |
|
|
const handleBatchSaveInputted = useCallback(async () => { |
|
|
const handleBatchSaveInputted = useCallback(async () => { |
|
|
if (!selectedSession || !currentUserId || batchInFlightRef.current) return; |
|
|
if (!selectedSession || !currentUserId || batchInFlightRef.current) return; |
|
|
|
|
|
|
|
|
@@ -495,7 +542,7 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
<Box sx={{ pb: 10 }}> |
|
|
<Box sx={{ pb: 10 }}> |
|
|
<Button |
|
|
<Button |
|
|
onClick={onBack} |
|
|
onClick={onBack} |
|
|
sx={{ mb: 2, border: "1px solid", borderColor: "primary.main" }} |
|
|
|
|
|
|
|
|
sx={{ mb: 2, minHeight: 48, px: 2, border: "1px solid", borderColor: "primary.main" }} |
|
|
> |
|
|
> |
|
|
{t("Back to List")} |
|
|
{t("Back to List")} |
|
|
</Button> |
|
|
</Button> |
|
|
@@ -584,8 +631,18 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
detail.firstStockTakeQty != null && |
|
|
detail.firstStockTakeQty != null && |
|
|
detail.secondStockTakeQty == null; |
|
|
detail.secondStockTakeQty == null; |
|
|
|
|
|
|
|
|
|
|
|
const canEditQty = !submitDisabled && (isFirstSubmit || isSecondSubmit); |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<TableRow key={detail.id}> |
|
|
|
|
|
|
|
|
<TableRow |
|
|
|
|
|
key={detail.id} |
|
|
|
|
|
hover={canEditQty} |
|
|
|
|
|
onClick={canEditQty ? onQtyRowClick(detail.id) : undefined} |
|
|
|
|
|
sx={{ |
|
|
|
|
|
"& .MuiTableCell-root": { py: 1.5 }, |
|
|
|
|
|
...(canEditQty ? { cursor: "pointer" } : {}), |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
<TableCell> |
|
|
<TableCell> |
|
|
{detail.warehouseArea || "-"} |
|
|
{detail.warehouseArea || "-"} |
|
|
{detail.warehouseSlot || "-"} |
|
|
{detail.warehouseSlot || "-"} |
|
|
@@ -624,16 +681,27 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
<Typography variant="body2">{t("First")}:</Typography> |
|
|
<Typography variant="body2">{t("First")}:</Typography> |
|
|
<TextField |
|
|
<TextField |
|
|
size="small" |
|
|
size="small" |
|
|
type="number" |
|
|
|
|
|
|
|
|
type="text" |
|
|
value={recordInputs[detail.id]?.firstQty || ""} |
|
|
value={recordInputs[detail.id]?.firstQty || ""} |
|
|
onFocus={() => |
|
|
onFocus={() => |
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:first`]: false })) |
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:first`]: false })) |
|
|
} |
|
|
} |
|
|
onBlur={() => |
|
|
|
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:first`]: true })) |
|
|
|
|
|
} |
|
|
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} |
|
|
|
|
|
onKeyDown={blockNonIntegerKeys} |
|
|
|
|
|
|
|
|
onBlur={(e) => { |
|
|
|
|
|
const next = e.relatedTarget as HTMLElement | null; |
|
|
|
|
|
if (next?.dataset.stockTakeQty === "1") return; |
|
|
|
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:first`]: true })); |
|
|
|
|
|
}} |
|
|
|
|
|
inputRef={(el) => { |
|
|
|
|
|
if (el) qtyInputRefs.current.set(detail.id, el); |
|
|
|
|
|
else qtyInputRefs.current.delete(detail.id); |
|
|
|
|
|
}} |
|
|
|
|
|
inputProps={{ |
|
|
|
|
|
inputMode: "numeric", |
|
|
|
|
|
enterKeyHint: "next", |
|
|
|
|
|
pattern: "[0-9]*", |
|
|
|
|
|
"data-stock-take-qty": "1", |
|
|
|
|
|
}} |
|
|
|
|
|
onKeyDown={onQtyKeyDown(detail.id)} |
|
|
onChange={(e) => { |
|
|
onChange={(e) => { |
|
|
const clean = sanitizeStockTakeQtyInput(e.target.value); |
|
|
const clean = sanitizeStockTakeQtyInput(e.target.value); |
|
|
const val = clean; |
|
|
const val = clean; |
|
|
@@ -643,18 +711,7 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
InputProps={{ |
|
|
InputProps={{ |
|
|
endAdornment: stockTakeQtyEndAdornment(detail.uomShortDesc), |
|
|
endAdornment: stockTakeQtyEndAdornment(detail.uomShortDesc), |
|
|
}} |
|
|
}} |
|
|
sx={{ |
|
|
|
|
|
width: 148, |
|
|
|
|
|
minWidth: 148, |
|
|
|
|
|
"& .MuiInputBase-input": { |
|
|
|
|
|
height: "1.4375em", |
|
|
|
|
|
padding: "4px 8px", |
|
|
|
|
|
}, |
|
|
|
|
|
"& .MuiInputBase-input::placeholder": { |
|
|
|
|
|
color: "grey.400", // MUI light grey |
|
|
|
|
|
opacity: 1, |
|
|
|
|
|
}, |
|
|
|
|
|
}} |
|
|
|
|
|
|
|
|
sx={pickerQtyFieldSx} |
|
|
placeholder={t("Stock Take Qty")} |
|
|
placeholder={t("Stock Take Qty")} |
|
|
/> |
|
|
/> |
|
|
{/* |
|
|
{/* |
|
|
@@ -710,16 +767,27 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
<Typography variant="body2">{t("Second")}:</Typography> |
|
|
<Typography variant="body2">{t("Second")}:</Typography> |
|
|
<TextField |
|
|
<TextField |
|
|
size="small" |
|
|
size="small" |
|
|
type="number" |
|
|
|
|
|
|
|
|
type="text" |
|
|
value={recordInputs[detail.id]?.secondQty || ""} |
|
|
value={recordInputs[detail.id]?.secondQty || ""} |
|
|
onFocus={() => |
|
|
onFocus={() => |
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:second`]: false })) |
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:second`]: false })) |
|
|
} |
|
|
} |
|
|
onBlur={() => |
|
|
|
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:second`]: true })) |
|
|
|
|
|
} |
|
|
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} |
|
|
|
|
|
onKeyDown={blockNonIntegerKeys} |
|
|
|
|
|
|
|
|
onBlur={(e) => { |
|
|
|
|
|
const next = e.relatedTarget as HTMLElement | null; |
|
|
|
|
|
if (next?.dataset.stockTakeQty === "1") return; |
|
|
|
|
|
setGapCheckOpen((prev) => ({ ...prev, [`${detail.id}:second`]: true })); |
|
|
|
|
|
}} |
|
|
|
|
|
inputRef={(el) => { |
|
|
|
|
|
if (el) qtyInputRefs.current.set(detail.id, el); |
|
|
|
|
|
else qtyInputRefs.current.delete(detail.id); |
|
|
|
|
|
}} |
|
|
|
|
|
inputProps={{ |
|
|
|
|
|
inputMode: "numeric", |
|
|
|
|
|
enterKeyHint: "next", |
|
|
|
|
|
pattern: "[0-9]*", |
|
|
|
|
|
"data-stock-take-qty": "1", |
|
|
|
|
|
}} |
|
|
|
|
|
onKeyDown={onQtyKeyDown(detail.id)} |
|
|
onChange={(e) => { |
|
|
onChange={(e) => { |
|
|
const clean = sanitizeStockTakeQtyInput(e.target.value); |
|
|
const clean = sanitizeStockTakeQtyInput(e.target.value); |
|
|
const val = clean; |
|
|
const val = clean; |
|
|
@@ -729,14 +797,7 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
InputProps={{ |
|
|
InputProps={{ |
|
|
endAdornment: stockTakeQtyEndAdornment(detail.uomShortDesc), |
|
|
endAdornment: stockTakeQtyEndAdornment(detail.uomShortDesc), |
|
|
}} |
|
|
}} |
|
|
sx={{ |
|
|
|
|
|
width: 148, |
|
|
|
|
|
minWidth: 148, |
|
|
|
|
|
"& .MuiInputBase-input": { |
|
|
|
|
|
height: "1.4375em", |
|
|
|
|
|
padding: "4px 8px", |
|
|
|
|
|
}, |
|
|
|
|
|
}} |
|
|
|
|
|
|
|
|
sx={pickerQtyFieldSx} |
|
|
placeholder={t("Stock Take Qty")} |
|
|
placeholder={t("Stock Take Qty")} |
|
|
/> |
|
|
/> |
|
|
{/* |
|
|
{/* |
|
|
@@ -797,8 +858,8 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ |
|
|
|
|
|
|
|
|
<Stack direction="row" spacing={1}> |
|
|
<Stack direction="row" spacing={1}> |
|
|
<Button |
|
|
<Button |
|
|
size="small" |
|
|
|
|
|
variant="contained" |
|
|
variant="contained" |
|
|
|
|
|
sx={pickerTouchButtonSx} |
|
|
onClick={() => handleSaveStockTake(detail)} |
|
|
onClick={() => handleSaveStockTake(detail)} |
|
|
disabled={saving || submitDisabled} |
|
|
disabled={saving || submitDisabled} |
|
|
> |
|
|
> |
|
|
|