FPSMS-frontend
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

PickerReStockTake.tsx 26 KiB

2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
2 ay önce
3 hafta önce
2 ay önce
2 ay önce
3 hafta önce
5 gün önce
5 gün önce
5 gün önce
2 ay önce
2 ay önce
2 ay önce
3 hafta önce
2 ay önce
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. "use client";
  2. import {
  3. Box,
  4. Button,
  5. Stack,
  6. Typography,
  7. Chip,
  8. CircularProgress,
  9. Table,
  10. TableBody,
  11. TableCell,
  12. TableContainer,
  13. TableHead,
  14. TableRow,
  15. Paper,
  16. TextField,
  17. TablePagination,
  18. } from "@mui/material";
  19. import { useState, useCallback, useEffect, useRef } from "react";
  20. import { useTranslation } from "react-i18next";
  21. import {
  22. AllPickedStockTakeListReponse,
  23. InventoryLotDetailResponse,
  24. saveStockTakeRecord,
  25. SaveStockTakeRecordRequest,
  26. BatchSaveStockTakeRecordRequest,
  27. batchSaveStockTakeRecords,
  28. getInventoryLotDetailsBySectionNotMatch
  29. } from "@/app/api/stockTake/actions";
  30. import { useSession } from "next-auth/react";
  31. import { SessionWithTokens } from "@/config/authConfig";
  32. import dayjs from "dayjs";
  33. import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  34. interface PickerReStockTakeProps {
  35. selectedSession: AllPickedStockTakeListReponse;
  36. onBack: () => void;
  37. onSnackbar: (message: string, severity: "success" | "error" | "warning") => void;
  38. }
  39. const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
  40. selectedSession,
  41. onBack,
  42. onSnackbar,
  43. }) => {
  44. const { t } = useTranslation(["inventory", "common"]);
  45. const { data: session } = useSession() as { data: SessionWithTokens | null };
  46. const [inventoryLotDetails, setInventoryLotDetails] = useState<InventoryLotDetailResponse[]>([]);
  47. const [loadingDetails, setLoadingDetails] = useState(false);
  48. const [recordInputs, setRecordInputs] = useState<Record<number, {
  49. firstQty: string;
  50. secondQty: string;
  51. firstBadQty: string;
  52. secondBadQty: string;
  53. remark: string;
  54. }>>({});
  55. const [saving, setSaving] = useState(false);
  56. const [batchSaving, setBatchSaving] = useState(false);
  57. const [shortcutInput, setShortcutInput] = useState<string>("");
  58. const [page, setPage] = useState(0);
  59. const [pageSize, setPageSize] = useState<number | string>("all");
  60. const [total, setTotal] = useState(0);
  61. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  62. const handleBatchSubmitAllRef = useRef<() => Promise<void>>();
  63. const handleChangePage = useCallback((event: unknown, newPage: number) => {
  64. setPage(newPage);
  65. }, []);
  66. const blockNonIntegerKeys = (e: React.KeyboardEvent<HTMLInputElement>) => {
  67. // 禁止小数点、逗号、科学计数、正负号
  68. if ([".", ",", "e", "E", "+", "-"].includes(e.key)) {
  69. e.preventDefault();
  70. }
  71. };
  72. const sanitizeIntegerInput = (value: string) => {
  73. // 只保留数字
  74. return value.replace(/[^\d]/g, "");
  75. };
  76. const isIntegerString = (value: string) => /^\d+$/.test(value);
  77. const handleChangeRowsPerPage = useCallback((event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
  78. const newSize = parseInt(event.target.value, 10);
  79. if (newSize === -1) {
  80. setPageSize("all");
  81. } else if (!isNaN(newSize)) {
  82. setPageSize(newSize);
  83. }
  84. setPage(0);
  85. }, []);
  86. const loadDetails = useCallback(async (pageNum: number, size: number | string) => {
  87. setLoadingDetails(true);
  88. try {
  89. let actualSize: number;
  90. if (size === "all") {
  91. if (selectedSession.totalInventoryLotNumber > 0) {
  92. actualSize = selectedSession.totalInventoryLotNumber;
  93. } else if (total > 0) {
  94. actualSize = total;
  95. } else {
  96. actualSize = 10000;
  97. }
  98. } else {
  99. actualSize = typeof size === 'string' ? parseInt(size, 10) : size;
  100. }
  101. const response = await getInventoryLotDetailsBySectionNotMatch(
  102. selectedSession.stockTakeSession,
  103. selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null,
  104. pageNum,
  105. actualSize
  106. );
  107. setInventoryLotDetails(Array.isArray(response.records) ? response.records : []);
  108. setTotal(response.total || 0);
  109. } catch (e) {
  110. console.error(e);
  111. setInventoryLotDetails([]);
  112. setTotal(0);
  113. } finally {
  114. setLoadingDetails(false);
  115. }
  116. }, [selectedSession, total]);
  117. useEffect(() => {
  118. const inputs: Record<number, { firstQty: string; secondQty: string; firstBadQty: string; secondBadQty: string; remark: string }> = {};
  119. inventoryLotDetails.forEach((detail) => {
  120. const firstTotal = detail.firstStockTakeQty != null
  121. ? (detail.firstStockTakeQty + (detail.firstBadQty ?? 0)).toString()
  122. : "";
  123. const secondTotal = detail.secondStockTakeQty != null
  124. ? (detail.secondStockTakeQty + (detail.secondBadQty ?? 0)).toString()
  125. : "";
  126. inputs[detail.id] = {
  127. firstQty: firstTotal,
  128. secondQty: secondTotal,
  129. firstBadQty: detail.firstBadQty?.toString() || "",
  130. secondBadQty: detail.secondBadQty?.toString() || "",
  131. remark: detail.remarks || "",
  132. };
  133. });
  134. setRecordInputs(inputs);
  135. }, [inventoryLotDetails]);
  136. useEffect(() => {
  137. loadDetails(page, pageSize);
  138. }, [page, pageSize, loadDetails]);
  139. const formatNumber = (num: number | null | undefined): string => {
  140. if (num == null || Number.isNaN(num)) return "0";
  141. return num.toLocaleString("en-US", {
  142. minimumFractionDigits: 0,
  143. maximumFractionDigits: 0,
  144. });
  145. };
  146. const handleSaveStockTake = useCallback(async (detail: InventoryLotDetailResponse) => {
  147. if (!selectedSession || !currentUserId) {
  148. return;
  149. }
  150. const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty;
  151. const isSecondSubmit = detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty;
  152. // 用戶輸入為 total 和 bad,需計算 available = total - bad(與 PickerStockTake 一致)
  153. const totalQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstQty : recordInputs[detail.id]?.secondQty;
  154. const badQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstBadQty : recordInputs[detail.id]?.secondBadQty;
  155. if (!totalQtyStr) {
  156. onSnackbar(
  157. isFirstSubmit
  158. ? t("Please enter QTY")
  159. : t("Please enter Second QTY"),
  160. "error"
  161. );
  162. return;
  163. }
  164. const totalQty = parseFloat(totalQtyStr);
  165. const badQty = parseFloat(badQtyStr || "0") || 0;
  166. if (Number.isNaN(totalQty)) {
  167. onSnackbar(t("Invalid QTY"), "error");
  168. return;
  169. }
  170. const availableQty = totalQty - badQty;
  171. if (availableQty < 0) {
  172. onSnackbar(t("Available QTY cannot be negative"), "error");
  173. return;
  174. }
  175. setSaving(true);
  176. try {
  177. const request: SaveStockTakeRecordRequest = {
  178. stockTakeRecordId: detail.stockTakeRecordId || null,
  179. inventoryLotLineId: detail.id,
  180. qty: availableQty,
  181. badQty: badQty,
  182. remark: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : null,
  183. };
  184. const result = await saveStockTakeRecord(
  185. request,
  186. selectedSession.stockTakeId,
  187. currentUserId
  188. );
  189. onSnackbar(t("Stock take record saved successfully"), "success");
  190. const savedId = result?.id ?? detail.stockTakeRecordId;
  191. setInventoryLotDetails((prev) =>
  192. prev.map((d) =>
  193. d.id === detail.id
  194. ? {
  195. ...d,
  196. stockTakeRecordId: savedId ?? d.stockTakeRecordId,
  197. firstStockTakeQty: isFirstSubmit ? availableQty : d.firstStockTakeQty,
  198. firstBadQty: isFirstSubmit ? badQty : d.firstBadQty ?? null,
  199. secondStockTakeQty: isSecondSubmit ? availableQty : d.secondStockTakeQty,
  200. secondBadQty: isSecondSubmit ? badQty : d.secondBadQty ?? null,
  201. remarks: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : d.remarks,
  202. stockTakeRecordStatus: "pass",
  203. }
  204. : d
  205. )
  206. );
  207. } catch (e: any) {
  208. console.error("Save stock take record error:", e);
  209. let errorMessage = t("Failed to save stock take record");
  210. if (e?.message) {
  211. errorMessage = e.message;
  212. } else if (e?.response) {
  213. try {
  214. const errorData = await e.response.json();
  215. errorMessage = errorData.message || errorData.error || errorMessage;
  216. } catch {
  217. // ignore
  218. }
  219. }
  220. onSnackbar(errorMessage, "error");
  221. } finally {
  222. setSaving(false);
  223. }
  224. }, [selectedSession, recordInputs, t, currentUserId, onSnackbar, page, pageSize, loadDetails]);
  225. const handleBatchSubmitAll = useCallback(async () => {
  226. if (!selectedSession || !currentUserId) {
  227. return;
  228. }
  229. setBatchSaving(true);
  230. try {
  231. const request: BatchSaveStockTakeRecordRequest = {
  232. stockTakeId: selectedSession.stockTakeId,
  233. stockTakeSection: selectedSession.stockTakeSession,
  234. stockTakerId: currentUserId,
  235. };
  236. const result = await batchSaveStockTakeRecords(request);
  237. onSnackbar(
  238. t("Batch save completed: {{success}} success, {{errors}} errors", {
  239. success: result.successCount,
  240. errors: result.errorCount,
  241. }),
  242. result.errorCount > 0 ? "warning" : "success"
  243. );
  244. await loadDetails(page, pageSize);
  245. } catch (e: any) {
  246. console.error("handleBatchSubmitAll: Error:", e);
  247. let errorMessage = t("Failed to batch save stock take records");
  248. if (e?.message) {
  249. errorMessage = e.message;
  250. } else if (e?.response) {
  251. try {
  252. const errorData = await e.response.json();
  253. errorMessage = errorData.message || errorData.error || errorMessage;
  254. } catch {
  255. // ignore
  256. }
  257. }
  258. onSnackbar(errorMessage, "error");
  259. } finally {
  260. setBatchSaving(false);
  261. }
  262. }, [selectedSession, t, currentUserId, onSnackbar, page, pageSize, loadDetails]);
  263. useEffect(() => {
  264. handleBatchSubmitAllRef.current = handleBatchSubmitAll;
  265. }, [handleBatchSubmitAll]);
  266. useEffect(() => {
  267. const handleKeyPress = (e: KeyboardEvent) => {
  268. const target = e.target as HTMLElement;
  269. if (target && (
  270. target.tagName === 'INPUT' ||
  271. target.tagName === 'TEXTAREA' ||
  272. target.isContentEditable
  273. )) {
  274. return;
  275. }
  276. if (e.ctrlKey || e.metaKey || e.altKey) {
  277. return;
  278. }
  279. if (e.key.length === 1) {
  280. setShortcutInput(prev => {
  281. const newInput = prev + e.key;
  282. if (newInput === '{2fitestall}') {
  283. setTimeout(() => {
  284. if (handleBatchSubmitAllRef.current) {
  285. handleBatchSubmitAllRef.current().catch(err => {
  286. console.error('Error in handleBatchSubmitAll:', err);
  287. });
  288. }
  289. }, 0);
  290. return "";
  291. }
  292. if (newInput.length > 15) return "";
  293. if (newInput.length > 0 && !newInput.startsWith('{')) return "";
  294. if (newInput.length > 5 && !newInput.startsWith('{2fi')) return "";
  295. return newInput;
  296. });
  297. } else if (e.key === 'Backspace') {
  298. setShortcutInput(prev => prev.slice(0, -1));
  299. } else if (e.key === 'Escape') {
  300. setShortcutInput("");
  301. }
  302. };
  303. window.addEventListener('keydown', handleKeyPress);
  304. return () => {
  305. window.removeEventListener('keydown', handleKeyPress);
  306. };
  307. }, []);
  308. const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => {
  309. if (selectedSession?.status?.toLowerCase() === "completed") {
  310. return true;
  311. }
  312. const recordStatus = detail.stockTakeRecordStatus?.toLowerCase();
  313. if (recordStatus === "pass" || recordStatus === "completed") {
  314. return true;
  315. }
  316. return false;
  317. }, [selectedSession?.status]);
  318. const uniqueWarehouses = Array.from(
  319. new Set(
  320. inventoryLotDetails
  321. .map(detail => detail.warehouse)
  322. .filter(warehouse => warehouse && warehouse.trim() !== "")
  323. )
  324. ).join(", ");
  325. const defaultInputs = { firstQty: "", secondQty: "", firstBadQty: "", secondBadQty: "", remark: "" };
  326. return (
  327. <Box>
  328. <Button onClick={onBack} sx={{ mb: 2, border: "1px solid", borderColor: "primary.main" }}>
  329. {t("Back to List")}
  330. </Button>
  331. <Typography variant="h6" sx={{ mb: 2 }}>
  332. {t("Stock Take Section")}: {selectedSession.stockTakeSession}
  333. {uniqueWarehouses && (
  334. <> {t("Warehouse")}: {uniqueWarehouses}</>
  335. )}
  336. </Typography>
  337. {loadingDetails ? (
  338. <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
  339. <CircularProgress />
  340. </Box>
  341. ) : (
  342. <>
  343. <TablePagination
  344. component="div"
  345. count={total}
  346. page={page}
  347. onPageChange={handleChangePage}
  348. rowsPerPage={pageSize === "all" ? total : (pageSize as number)}
  349. onRowsPerPageChange={handleChangeRowsPerPage}
  350. rowsPerPageOptions={[10, 25, 50, 100, { value: -1, label: t("All") }]}
  351. labelRowsPerPage={t("Rows per page")}
  352. />
  353. <TableContainer component={Paper}>
  354. <Table>
  355. <TableHead>
  356. <TableRow>
  357. <TableCell>{t("Warehouse Location")}</TableCell>
  358. <TableCell>{t("Item-lotNo-ExpiryDate")}</TableCell>
  359. <TableCell>{t("UOM")}</TableCell>
  360. <TableCell>{t("Stock Take Qty(include Bad Qty)= Available Qty")}</TableCell>
  361. <TableCell>{t("Action")}</TableCell>
  362. <TableCell>{t("Remark")}</TableCell>
  363. <TableCell>{t("Record Status")}</TableCell>
  364. </TableRow>
  365. </TableHead>
  366. <TableBody>
  367. {inventoryLotDetails.length === 0 ? (
  368. <TableRow>
  369. <TableCell colSpan={7} align="center">
  370. <Typography variant="body2" color="text.secondary">
  371. {t("No data")}
  372. </Typography>
  373. </TableCell>
  374. </TableRow>
  375. ) : (
  376. inventoryLotDetails.map((detail) => {
  377. const submitDisabled = isSubmitDisabled(detail);
  378. const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty;
  379. const isSecondSubmit = detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty;
  380. const inputs = recordInputs[detail.id] ?? defaultInputs;
  381. return (
  382. <TableRow key={detail.id}>
  383. <TableCell>{detail.warehouseArea || "-"}{detail.warehouseSlot || "-"}</TableCell>
  384. <TableCell sx={{
  385. maxWidth: 150,
  386. wordBreak: 'break-word',
  387. whiteSpace: 'normal',
  388. lineHeight: 1.5
  389. }}>
  390. <Stack spacing={0.5}>
  391. <Box>{detail.itemCode || "-"} {detail.itemName || "-"}</Box>
  392. <Box>{detail.lotNo || "-"}</Box>
  393. <Box>{detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"}</Box>
  394. </Stack>
  395. </TableCell>
  396. <TableCell>{detail.uom || "-"}</TableCell>
  397. <TableCell sx={{ minWidth: 300 }}>
  398. <Stack spacing={1}>
  399. {/* First */}
  400. {!submitDisabled && isFirstSubmit ? (
  401. <Stack direction="row" spacing={1} alignItems="center">
  402. <Typography variant="body2">{t("First")}:</Typography>
  403. <TextField
  404. size="small"
  405. type="number"
  406. value={inputs.firstQty}
  407. inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
  408. onKeyDown={blockNonIntegerKeys}
  409. onChange={(e) => {
  410. const clean = sanitizeIntegerInput(e.target.value);
  411. const val = clean;
  412. if (val.includes("-")) return;
  413. setRecordInputs(prev => ({
  414. ...prev,
  415. [detail.id]: { ...(prev[detail.id] ?? defaultInputs), firstQty: val }
  416. }));
  417. }}
  418. sx={{
  419. width: 130,
  420. minWidth: 130,
  421. "& .MuiInputBase-input": {
  422. height: "1.4375em",
  423. padding: "4px 8px",
  424. },
  425. }}
  426. placeholder={t("Stock Take Qty")}
  427. />
  428. <TextField
  429. size="small"
  430. type="number"
  431. value={inputs.firstBadQty}
  432. inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
  433. onKeyDown={blockNonIntegerKeys}
  434. onChange={(e) => {
  435. const clean = sanitizeIntegerInput(e.target.value);
  436. const val = clean;
  437. if (val.includes("-")) return;
  438. setRecordInputs(prev => ({
  439. ...prev,
  440. [detail.id]: { ...(prev[detail.id] ?? defaultInputs), firstBadQty: val }
  441. }));
  442. }}
  443. sx={{
  444. width: 130,
  445. minWidth: 130,
  446. "& .MuiInputBase-input": {
  447. height: "1.4375em",
  448. padding: "4px 8px",
  449. },
  450. }}
  451. placeholder={t("Bad Qty")}
  452. />
  453. <Typography variant="body2">
  454. = {formatNumber(parseFloat(inputs.firstQty || "0") - parseFloat(inputs.firstBadQty || "0"))}
  455. </Typography>
  456. </Stack>
  457. ) : detail.firstStockTakeQty != null ? (
  458. <Typography variant="body2">
  459. {t("First")}:{" "}
  460. {formatNumber((detail.firstStockTakeQty ?? 0) + (detail.firstBadQty ?? 0))}{" "}
  461. ({formatNumber(detail.firstBadQty ?? 0)}) ={" "}
  462. {formatNumber(detail.firstStockTakeQty ?? 0)}
  463. </Typography>
  464. ) : null}
  465. {/* Second */}
  466. {!submitDisabled && isSecondSubmit ? (
  467. <Stack direction="row" spacing={1} alignItems="center">
  468. <Typography variant="body2">{t("Second")}:</Typography>
  469. <TextField
  470. size="small"
  471. type="number"
  472. value={inputs.secondQty}
  473. inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
  474. onKeyDown={blockNonIntegerKeys}
  475. onChange={(e) => {
  476. const clean = sanitizeIntegerInput(e.target.value);
  477. const val = clean;
  478. if (val.includes("-")) return;
  479. setRecordInputs(prev => ({
  480. ...prev,
  481. [detail.id]: { ...(prev[detail.id] ?? defaultInputs), secondQty: clean }
  482. }));
  483. }}
  484. sx={{
  485. width: 130,
  486. minWidth: 130,
  487. "& .MuiInputBase-input": {
  488. height: "1.4375em",
  489. padding: "4px 8px",
  490. },
  491. }}
  492. placeholder={t("Stock Take Qty")}
  493. />
  494. <TextField
  495. size="small"
  496. type="number"
  497. value={inputs.secondBadQty}
  498. inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
  499. onKeyDown={blockNonIntegerKeys}
  500. onChange={(e) => {
  501. const clean = sanitizeIntegerInput(e.target.value);
  502. const val = clean;
  503. if (val.includes("-")) return;
  504. setRecordInputs(prev => ({
  505. ...prev,
  506. [detail.id]: { ...(prev[detail.id] ?? defaultInputs), secondBadQty: clean }
  507. }));
  508. }}
  509. sx={{
  510. width: 130,
  511. minWidth: 130,
  512. "& .MuiInputBase-input": {
  513. height: "1.4375em",
  514. padding: "4px 8px",
  515. },
  516. }}
  517. placeholder={t("Bad Qty")}
  518. />
  519. <Typography variant="body2">
  520. = {formatNumber(parseFloat(inputs.secondQty || "0") - parseFloat(inputs.secondBadQty || "0"))}
  521. </Typography>
  522. </Stack>
  523. ) : detail.secondStockTakeQty != null ? (
  524. <Typography variant="body2">
  525. {t("Second")}:{" "}
  526. {formatNumber((detail.secondStockTakeQty ?? 0) + (detail.secondBadQty ?? 0))}{" "}
  527. ({formatNumber(detail.secondBadQty ?? 0)}) ={" "}
  528. {formatNumber(detail.secondStockTakeQty ?? 0)}
  529. </Typography>
  530. ) : null}
  531. {!detail.firstStockTakeQty && !detail.secondStockTakeQty && !submitDisabled && (
  532. <Typography variant="body2" color="text.secondary">
  533. -
  534. </Typography>
  535. )}
  536. </Stack>
  537. </TableCell>
  538. <TableCell>
  539. <Stack direction="row" spacing={1}>
  540. <Button
  541. size="small"
  542. variant="contained"
  543. onClick={() => handleSaveStockTake(detail)}
  544. disabled={saving || submitDisabled }
  545. >
  546. {t("Save")}
  547. </Button>
  548. </Stack>
  549. </TableCell>
  550. <TableCell sx={{ width: 180 }}>
  551. {!submitDisabled && isSecondSubmit ? (
  552. <>
  553. <Typography variant="body2">{t("Remark")}</Typography>
  554. <TextField
  555. size="small"
  556. value={inputs.remark}
  557. // onKeyDown={blockNonIntegerKeys}
  558. //inputProps={{ inputMode: "text", pattern: "[0-9]*" }}
  559. onChange={(e) => {
  560. // const clean = sanitizeIntegerInput(e.target.value);
  561. setRecordInputs(prev => ({
  562. ...prev,
  563. [detail.id]: { ...(prev[detail.id] ?? defaultInputs), remark: e.target.value }
  564. }));
  565. }}
  566. sx={{ width: 150 }}
  567. />
  568. </>
  569. ) : (
  570. <Typography variant="body2">
  571. {detail.remarks || "-"}
  572. </Typography>
  573. )}
  574. </TableCell>
  575. <TableCell>
  576. {detail.stockTakeRecordStatus === "completed" ? (
  577. <Chip size="small" label={t(detail.stockTakeRecordStatus)} color="success" />
  578. ) : detail.stockTakeRecordStatus === "pass" ? (
  579. <Chip size="small" label={t(detail.stockTakeRecordStatus)} color="default" />
  580. ) : detail.stockTakeRecordStatus === "notMatch" ? (
  581. <Chip size="small" label={t(detail.stockTakeRecordStatus)} color="warning" />
  582. ) : (
  583. <Chip size="small" label={t(detail.stockTakeRecordStatus || "")} color="default" />
  584. )}
  585. </TableCell>
  586. </TableRow>
  587. );
  588. })
  589. )}
  590. </TableBody>
  591. </Table>
  592. </TableContainer>
  593. <TablePagination
  594. component="div"
  595. count={total}
  596. page={page}
  597. onPageChange={handleChangePage}
  598. rowsPerPage={pageSize === "all" ? total : (pageSize as number)}
  599. onRowsPerPageChange={handleChangeRowsPerPage}
  600. rowsPerPageOptions={[10, 25, 50, 100, { value: -1, label: t("All") }]}
  601. labelRowsPerPage={t("Rows per page")}
  602. />
  603. </>
  604. )}
  605. </Box>
  606. );
  607. };
  608. export default PickerReStockTake;