FPSMS-frontend
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 

900 wiersze
32 KiB

  1. "use client";
  2. import {
  3. fetchPoWithStockInLines,
  4. PoResult,
  5. PurchaseOrderLine,
  6. } from "@/app/api/po";
  7. import {
  8. Box,
  9. Button,
  10. ButtonProps,
  11. Collapse,
  12. Grid,
  13. IconButton,
  14. Paper,
  15. Stack,
  16. Tab,
  17. Table,
  18. TableBody,
  19. TableCell,
  20. TableContainer,
  21. TableHead,
  22. TableRow,
  23. Tabs,
  24. TabsProps,
  25. TextField,
  26. Typography,
  27. Checkbox,
  28. FormControlLabel,
  29. Card,
  30. CardContent,
  31. Radio,
  32. } from "@mui/material";
  33. import { useTranslation } from "react-i18next";
  34. // import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid";
  35. import {
  36. GridColDef,
  37. GridRowId,
  38. GridRowModel,
  39. useGridApiRef,
  40. } from "@mui/x-data-grid";
  41. import {
  42. checkPolAndCompletePo,
  43. fetchPoInClient,
  44. fetchPoListClient,
  45. startPo,
  46. } from "@/app/api/po/actions";
  47. import {
  48. createStockInLine
  49. } from "@/app/api/stockIn/actions";
  50. import {
  51. useCallback,
  52. useContext,
  53. useEffect,
  54. useMemo,
  55. useState,
  56. } from "react";
  57. import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
  58. import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
  59. import PoInputGrid from "./PoInputGrid";
  60. // import { QcItemWithChecks } from "@/app/api/qc";
  61. import { useRouter, useSearchParams, usePathname } from "next/navigation";
  62. import { WarehouseResult } from "@/app/api/warehouse";
  63. import { calculateWeight, dateStringToDayjs, dayjsToDateString, OUTPUT_DATE_FORMAT, outputDateStringToInputDateString, returnWeightUnit } from "@/app/utils/formatUtil";
  64. import { CameraContext } from "../Cameras/CameraProvider";
  65. import QrModal from "./QrModal";
  66. import { PlayArrow } from "@mui/icons-material";
  67. import DoneIcon from "@mui/icons-material/Done";
  68. import { downloadFile, getCustomWidth } from "@/app/utils/commonUtil";
  69. import PoInfoCard from "./PoInfoCard";
  70. import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
  71. import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material";
  72. import { Controller, FormProvider, useForm } from "react-hook-form";
  73. import dayjs, { Dayjs } from "dayjs";
  74. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  75. import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers";
  76. import { debounce } from "lodash";
  77. import LoadingComponent from "../General/LoadingComponent";
  78. import { getMailTemplatePdfForStockInLine } from "@/app/api/mailTemplate/actions";
  79. import { PrinterCombo } from "@/app/api/settings/printer";
  80. import { EscalationCombo } from "@/app/api/user";
  81. import { StockInLine } from "@/app/api/stockIn";
  82. //import { useRouter } from "next/navigation";
  83. type Props = {
  84. po: PoResult;
  85. // qc: QcItemWithChecks[];
  86. warehouse: WarehouseResult[];
  87. printerCombo: PrinterCombo[];
  88. };
  89. type EntryError =
  90. | {
  91. [field in keyof StockInLine]?: string;
  92. }
  93. | undefined;
  94. // type PolRow = TableRow<Partial<StockInLine>, EntryError>;
  95. const PoSearchList: React.FC<{
  96. poList: PoResult[];
  97. selectedPoId: number;
  98. onSelect: (po: PoResult) => void;
  99. }> = ({ poList, selectedPoId, onSelect }) => {
  100. const { t } = useTranslation(["purchaseOrder", "dashboard"]);
  101. const [searchTerm, setSearchTerm] = useState('');
  102. const filteredPoList = useMemo(() => {
  103. if (searchTerm.trim() === '') {
  104. return poList;
  105. }
  106. return poList.filter(poItem =>
  107. poItem.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
  108. poItem.supplier?.toLowerCase().includes(searchTerm.toLowerCase()) ||
  109. t(`${poItem.status.toLowerCase()}`).toLowerCase().includes(searchTerm.toLowerCase())
  110. );
  111. }, [poList, searchTerm, t]);
  112. return (
  113. <Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}>
  114. <Typography variant="h6" gutterBottom>
  115. {t("Purchase Order")}
  116. </Typography>
  117. <TextField
  118. label={t("Search")}
  119. variant="outlined"
  120. size="small"
  121. fullWidth
  122. value={searchTerm}
  123. onChange={(e) => setSearchTerm(e.target.value)}
  124. sx={{ mb: 2 }}
  125. InputProps={{
  126. startAdornment: (
  127. <Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
  128. </Typography>
  129. ),
  130. }}
  131. />
  132. {(filteredPoList.length > 0)? (
  133. <List dense sx={{ width: '100%' }}>
  134. {filteredPoList.map((poItem, index) => (
  135. <div key={poItem.id}>
  136. <ListItem disablePadding sx={{ width: '100%' }}>
  137. <ListItemButton
  138. selected={selectedPoId === poItem.id}
  139. onClick={() => onSelect(poItem)}
  140. sx={{
  141. width: '100%',
  142. "&.Mui-selected": {
  143. backgroundColor: "primary.light",
  144. "&:hover": {
  145. backgroundColor: "primary.light",
  146. },
  147. },
  148. }}
  149. >
  150. <ListItemText
  151. primary={
  152. <Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
  153. {poItem.code}
  154. </Typography>
  155. }
  156. secondary={
  157. <Typography variant="caption" color="text.secondary">
  158. {t(`${poItem.status.toLowerCase()}`)}
  159. </Typography>
  160. }
  161. />
  162. </ListItemButton>
  163. </ListItem>
  164. {index < filteredPoList.length - 1 && <Divider />}
  165. </div>
  166. ))}
  167. </List>) : (
  168. <LoadingComponent/>
  169. )
  170. }
  171. {searchTerm && (
  172. <Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}>
  173. {`${t("Found")} ${filteredPoList.length} ${t("Purchase Order")}`}
  174. {/* {`${t("Found")} ${filteredPoList.length} of ${poList.length} ${t("Item")}`} */}
  175. </Typography>
  176. )}
  177. </Paper>
  178. );
  179. };
  180. interface PolInputResult {
  181. lotNo: string,
  182. dnQty: number,
  183. }
  184. const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => {
  185. const cameras = useContext(CameraContext);
  186. // console.log(cameras);
  187. const { t } = useTranslation("purchaseOrder");
  188. const apiRef = useGridApiRef();
  189. const [purchaseOrder, setPurchaseOrder] = useState({ ...po });
  190. const [rows, setRows] = useState<PurchaseOrderLine[]>(
  191. purchaseOrder.pol || [],
  192. );
  193. const [polInputList, setPolInputList] = useState<PolInputResult[]>([])
  194. useEffect(() => {
  195. setPolInputList(
  196. (purchaseOrder.pol ?? []).map(() => ({
  197. lotNo: "",
  198. dnQty: 0,
  199. } as PolInputResult))
  200. );
  201. }, [purchaseOrder.pol]);
  202. const pathname = usePathname()
  203. const searchParams = useSearchParams();
  204. const [selectedRow, setSelectedRow] = useState<PurchaseOrderLine | null>(null);
  205. const defaultPolId = searchParams.get("polId")
  206. useEffect(() => {
  207. if (defaultPolId) {
  208. setSelectedRow(rows.find((r) => r.id.toString() === defaultPolId) ?? null)
  209. console.log("%c StockIn:", "color:green", selectedRow);
  210. }
  211. }, [])
  212. const [stockInLine, setStockInLine] = useState<StockInLine[]>([]);
  213. const [processedQty, setProcessedQty] = useState(0);
  214. const router = useRouter();
  215. const [poList, setPoList] = useState<PoResult[]>([]);
  216. const [selectedPoId, setSelectedPoId] = useState(po.id);
  217. const [focusField, setFocusField] = useState<HTMLInputElement>();
  218. const currentPoId = searchParams.get('id');
  219. const selectedIdsParam = searchParams.get('selectedIds');
  220. // const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
  221. const dnFormProps = useForm({
  222. defaultValues: {
  223. dnNo: '',
  224. receiptDate: dayjsToDateString(dayjs())
  225. }
  226. })
  227. const fetchPoList = useCallback(async () => {
  228. try {
  229. if (selectedIdsParam) {
  230. const selectedIds = selectedIdsParam.split(',').map(id => parseInt(id));
  231. const promises = selectedIds.map(id => fetchPoInClient(id));
  232. const results = await Promise.all(promises);
  233. setPoList(results.filter(Boolean));
  234. } else {
  235. const result = await fetchPoListClient({ limit: 20, offset: 0 });
  236. if (result && result.records) {
  237. setPoList(result.records);
  238. }
  239. }
  240. } catch (error) {
  241. console.error("Failed to fetch PO list:", error);
  242. }
  243. }, [selectedIdsParam]);
  244. const fetchPoDetail = useCallback(async (poId: string) => {
  245. try {
  246. const result = await fetchPoInClient(parseInt(poId));
  247. if (result) {
  248. console.log("%c Fetched PO:", "color:orange", result);
  249. setPurchaseOrder(result);
  250. setRows(result.pol || []);
  251. if (result.pol && result.pol.length > 0) {
  252. if (result.id === selectedPoId) {
  253. const polIndex = result.pol.findIndex((p) => p.id === selectedRow?.id)
  254. // setSelectedRow(result.pol[polIndex]);
  255. setStockInLine(result.pol[polIndex].stockInLine);
  256. setProcessedQty(result.pol[polIndex].processed);
  257. } else {
  258. // setSelectedRow(result.pol[0]);
  259. setStockInLine(result.pol[0].stockInLine);
  260. setProcessedQty(result.pol[0].processed);
  261. }
  262. }
  263. // if (focusField) {console.log(focusField);focusField.focus();}
  264. }
  265. } catch (error) {
  266. console.error("Failed to fetch PO detail:", error);
  267. }
  268. }, [selectedRow]);
  269. const handlePoSelect = useCallback(
  270. async (selectedPo: PoResult) => {
  271. if (selectedPo.id === selectedPoId) return;
  272. setSelectedPoId(selectedPo.id);
  273. await fetchPoDetail(selectedPo.id.toString());
  274. const newSelectedIds = selectedIdsParam || selectedPo.id.toString();
  275. // router.push(`/po/edit?id=${selectedPo.id}&start=true&selectedIds=${newSelectedIds}`, { scroll: false });
  276. const newUrl = `/po/edit?id=${selectedPo.id}&start=true&selectedIds=${newSelectedIds}`;
  277. if (pathname + searchParams.toString() !== newUrl) {
  278. router.replace(newUrl, { scroll: false });
  279. }
  280. },
  281. [router, selectedIdsParam, fetchPoDetail]
  282. );
  283. useEffect(() => {
  284. if (currentPoId && currentPoId !== selectedPoId.toString()) {
  285. setSelectedPoId(parseInt(currentPoId));
  286. fetchPoDetail(currentPoId);
  287. }
  288. }, [currentPoId, fetchPoDetail]);
  289. useEffect(() => {
  290. fetchPoList();
  291. }, [fetchPoList]);
  292. useEffect(() => {
  293. if (currentPoId) {
  294. setSelectedPoId(parseInt(currentPoId));
  295. }
  296. }, [currentPoId]);
  297. const removeParam = (paramToRemove: string) => {
  298. const newParams = new URLSearchParams(searchParams.toString());
  299. newParams.delete(paramToRemove);
  300. window.history.replaceState({}, '', `${window.location.pathname}?${newParams}`);
  301. };
  302. const handleCompletePo = useCallback(async () => {
  303. const checkRes = await checkPolAndCompletePo(purchaseOrder.id);
  304. console.log(checkRes);
  305. const newPo = await fetchPoInClient(purchaseOrder.id);
  306. setPurchaseOrder(newPo);
  307. }, [purchaseOrder.id]);
  308. const handleStartPo = useCallback(async () => {
  309. const startRes = await startPo(purchaseOrder.id);
  310. console.log(startRes);
  311. const newPo = await fetchPoInClient(purchaseOrder.id);
  312. setPurchaseOrder(newPo);
  313. }, [purchaseOrder.id]);
  314. const handleMailTemplateForStockInLine = useCallback(async (stockInLineId: number) => {
  315. const response = await getMailTemplatePdfForStockInLine(stockInLineId)
  316. if (response) {
  317. downloadFile(new Uint8Array(response.blobValue), response.filename);
  318. }
  319. }, [])
  320. useEffect(() => {
  321. setRows(purchaseOrder.pol || []);
  322. }, [purchaseOrder]);
  323. // useEffect(() => {
  324. // setStockInLine([])
  325. // }, []);
  326. function Row(props: { row: PurchaseOrderLine }) {
  327. const { row } = props;
  328. // const [firstReceiveQty, setFirstReceiveQty] = useState<number>()
  329. // const [secondReceiveQty, setSecondReceiveQty] = useState<number>()
  330. // const [open, setOpen] = useState(false);
  331. const [processedQty, setProcessedQty] = useState(row.processed);
  332. const [currStatus, setCurrStatus] = useState(row.status);
  333. // const [stockInLine, setStockInLine] = useState(row.stockInLine);
  334. const totalWeight = useMemo(
  335. () => calculateWeight(row.qty, row.uom),
  336. [row.qty, row.uom],
  337. );
  338. const weightUnit = useMemo(
  339. () => returnWeightUnit(row.uom),
  340. [row.uom],
  341. );
  342. const rowIndex = useMemo(() => {
  343. return rows.findIndex((r) => r.id === row.id)
  344. }, [])
  345. useEffect(() => {
  346. const polId = searchParams.get("polId") != null ? parseInt(searchParams.get("polId")!) : null
  347. if (polId) {
  348. setStockInLine(rows.find((r) => r.id == polId)!.stockInLine)
  349. }
  350. }, []);
  351. useEffect(() => {
  352. if (processedQty === row.qty) {
  353. setCurrStatus("completed".toUpperCase());
  354. } else if (processedQty > 0) {
  355. setCurrStatus("receiving".toUpperCase());
  356. } else {
  357. setCurrStatus("pending".toUpperCase());
  358. }
  359. }, [processedQty, row.qty]);
  360. const handleRowSelect = () => {
  361. // setSelectedRowId(row.id);
  362. setSelectedRow(row);
  363. setStockInLine(row.stockInLine);
  364. setProcessedQty(row.processed);
  365. };
  366. const changeStockInLines = useCallback(
  367. (id: number) => {
  368. //rows = purchaseOrderLine
  369. const target = rows.find((r) => r.id === id)
  370. const stockInLine = target!.stockInLine
  371. setStockInLine(stockInLine)
  372. setSelectedRow(target!)
  373. // console.log(pathname)
  374. // router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`);
  375. },
  376. [rows]
  377. );
  378. const handleStart = useCallback(
  379. () => {
  380. setTimeout(async () => {
  381. // post stock in line
  382. const oldId = row.id;
  383. const acceptedQty = Number(polInputList[rowIndex].dnQty);
  384. if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update
  385. const postData = {
  386. dnNo: dnFormProps.watch("dnNo"),
  387. receiptDate: outputDateStringToInputDateString(dnFormProps.watch("receiptDate")),
  388. itemId: row.itemId,
  389. itemNo: row.itemNo,
  390. itemName: row.itemName,
  391. // purchaseOrderId: row.purchaseOrderId,
  392. purchaseOrderLineId: row.id,
  393. acceptedQty: acceptedQty,
  394. productLotNo: polInputList[rowIndex].lotNo || '',
  395. // acceptedQty: secondReceiveQty || 0,
  396. // acceptedQty: row.acceptedQty,
  397. };
  398. // if (secondReceiveQty === 0) return
  399. const res = await createStockInLine(postData);
  400. if (res) {
  401. fetchPoDetail(selectedPoId.toString());
  402. }
  403. console.log(res);
  404. }, 200);
  405. },
  406. [polInputList, row, dnFormProps],
  407. );
  408. const handleChange = useCallback(debounce((e: React.ChangeEvent<HTMLInputElement>) => {
  409. const raw = e.target.value;
  410. const id = e.target.id
  411. // const temp = polInputList
  412. switch (id) {
  413. case "lotNo":
  414. if (raw.trim() === '') {
  415. polInputList[rowIndex].lotNo = '';
  416. break;
  417. }
  418. polInputList[rowIndex].lotNo = raw.trim();
  419. break;
  420. case "dnQty":
  421. // Allow empty input
  422. if (raw.trim() === '') {
  423. polInputList[rowIndex].dnQty = 0;
  424. break;
  425. }
  426. // Keep digits only
  427. const cleaned = raw.replace(/[^\d]/g, '');
  428. if (cleaned === '') {
  429. // If the user typed only non-digits, keep previous value
  430. break;
  431. }
  432. // Parse and clamp to non-negative integer
  433. const next = Math.max(0, Math.floor(Number(cleaned)));
  434. polInputList[rowIndex].dnQty = next;
  435. break;
  436. default:
  437. break;
  438. }
  439. // setPolInputList(() => temp)
  440. }, 300), [rowIndex]);
  441. // const [focusField, setFocusField] = useState<HTMLInputElement>();
  442. const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1)
  443. const receivedTotal = decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio);
  444. const highlightColor = (Number(receivedTotal.replace(/,/g, '')) <= 0) ? "red" : "inherit";
  445. return (
  446. <>
  447. <TableRow
  448. sx={{ "& > *": { borderBottom: "unset" },
  449. color: "black"
  450. }}
  451. onClick={() => changeStockInLines(row.id)}
  452. >
  453. {/* <TableCell>
  454. <IconButton
  455. disabled={purchaseOrder.status.toLowerCase() === "pending"}
  456. aria-label="expand row"
  457. size="small"
  458. onClick={() => setOpen(!open)}
  459. >
  460. {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
  461. </IconButton>
  462. </TableCell> */}
  463. <TableCell align="center" sx={{ width: '60px' }}>
  464. <Radio
  465. checked={selectedRow?.id === row.id}
  466. // onChange={handleRowSelect}
  467. // onClick={(e) => e.stopPropagation()}
  468. />
  469. </TableCell>
  470. <TableCell align="left">{row.itemNo}</TableCell>
  471. <TableCell align="left">{row.itemName}</TableCell>
  472. <TableCell align="right">{integerFormatter.format(row.qty)}</TableCell>
  473. <TableCell align="right">{integerFormatter.format(row.processed)}</TableCell>
  474. <TableCell align="left">{row.uom?.udfudesc}</TableCell>
  475. {/* <TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</TableCell> */}
  476. <TableCell sx={{ color: highlightColor}} align="right">{receivedTotal}</TableCell>
  477. <TableCell sx={{ color: highlightColor}} align="left">{row.stockUom.stockUomDesc}</TableCell>
  478. {/* <TableCell align="right">
  479. {decimalFormatter.format(totalWeight)} {weightUnit}
  480. </TableCell> */}
  481. {/* <TableCell align="left">{weightUnit}</TableCell> */}
  482. {/* <TableCell align="right">{decimalFormatter.format(row.price)}</TableCell> */}
  483. {/* <TableCell align="left">{row.expiryDate}</TableCell> */}
  484. <TableCell sx={{ color: highlightColor}} align="left">{t(`${row.status.toLowerCase()}`)}</TableCell>
  485. {/* <TableCell sx={{ color: highlightColor}} align="left">{t(`${currStatus.toLowerCase()}`)}</TableCell> */}
  486. {/* <TableCell align="right">{integerFormatter.format(row.receivedQty)}</TableCell> */}
  487. <TableCell align="center">
  488. <TextField
  489. id="lotNo"
  490. label="輸入貨品批號"
  491. type="text" // Use type="text" to allow validation in the change handler
  492. variant="outlined"
  493. defaultValue={polInputList[rowIndex]?.lotNo ?? ''}
  494. onChange={handleChange}
  495. // onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}}
  496. />
  497. </TableCell>
  498. <TableCell align="center">
  499. <TextField
  500. id="dnQty"
  501. label="此批送貨數量"
  502. type="text" // Use type="text" to allow validation in the change handler
  503. variant="outlined"
  504. defaultValue={polInputList[rowIndex]?.dnQty ?? ''}
  505. onChange={handleChange}
  506. InputProps={{
  507. inputProps: {
  508. min: 0, // Optional: set a minimum value
  509. step: 1 // Optional: set the step for the number input
  510. }
  511. }}
  512. />
  513. </TableCell>
  514. <TableCell align="center">
  515. <Button
  516. variant="contained"
  517. onClick={() =>
  518. handleStart()
  519. }
  520. >
  521. {t("submit")}
  522. </Button>
  523. </TableCell>
  524. </TableRow>
  525. {/* <TableRow> */}
  526. {/* <TableCell /> */}
  527. {/* <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={12}> */}
  528. {/* <Collapse in={true} timeout="auto" unmountOnExit> */}
  529. {/* <Collapse in={open} timeout="auto" unmountOnExit> */}
  530. {/* <Table>
  531. <TableBody>
  532. <TableRow>
  533. <TableCell align="right">
  534. <Box>
  535. <PoInputGrid
  536. qc={qc}
  537. setRows={setRows}
  538. stockInLine={stockInLine}
  539. setStockInLine={setStockInLine}
  540. setProcessedQty={setProcessedQty}
  541. itemDetail={row}
  542. warehouse={warehouse}
  543. />
  544. </Box>
  545. </TableCell>
  546. </TableRow>
  547. </TableBody>
  548. </Table> */}
  549. {/* </Collapse> */}
  550. {/* </TableCell> */}
  551. {/* </TableRow> */}
  552. </>
  553. );
  554. }
  555. // ROW END
  556. const [tabIndex, setTabIndex] = useState(0);
  557. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  558. (_e, newValue) => {
  559. setTabIndex(newValue);
  560. },
  561. [],
  562. );
  563. const [isOpenScanner, setOpenScanner] = useState(false);
  564. // const testing = useCallback(() => {
  565. // // setOpenScanner(true);
  566. // const newParams = new URLSearchParams(searchParams.toString());
  567. // console.log(pathname)
  568. // }, [pathname, router, searchParams]);
  569. const onOpenScanner = useCallback(() => {
  570. setOpenScanner(true);
  571. }, []);
  572. const onCloseScanner = useCallback(() => {
  573. setOpenScanner(false);
  574. }, []);
  575. const [itemInfo, setItemInfo] = useState<
  576. StockInLine & { warehouseId?: number }
  577. >();
  578. const [putAwayOpen, setPutAwayOpen] = useState(false);
  579. // const [scannedInfo, setScannedInfo] = useState<QrCodeInfo>({} as QrCodeInfo);
  580. const closePutAwayModal = useCallback(() => {
  581. setPutAwayOpen(false);
  582. setItemInfo(undefined);
  583. }, []);
  584. const openPutAwayModal = useCallback(() => {
  585. setPutAwayOpen(true);
  586. }, []);
  587. const buttonData = useMemo(() => {
  588. switch (purchaseOrder.status.toLowerCase()) {
  589. case "pending":
  590. return {
  591. buttonName: "start",
  592. title: t("Do you want to start?"),
  593. confirmButtonText: t("Start"),
  594. successTitle: t("Start Success"),
  595. errorTitle: t("Start Fail"),
  596. buttonText: t("Start PO"),
  597. buttonIcon: <PlayArrow />,
  598. buttonColor: "success",
  599. disabled: false,
  600. onClick: handleStartPo,
  601. };
  602. case "receiving":
  603. return {
  604. buttonName: "complete",
  605. title: t("Do you want to complete?"),
  606. confirmButtonText: t("Complete"),
  607. successTitle: t("Complete Success"),
  608. errorTitle: t("Complete Fail"),
  609. buttonText: t("Complete PO"),
  610. buttonIcon: <DoneIcon />,
  611. buttonColor: "info",
  612. disabled: false,
  613. onClick: handleCompletePo,
  614. };
  615. default:
  616. return {
  617. buttonName: "complete",
  618. title: t("Do you want to complete?"),
  619. confirmButtonText: t("Complete"),
  620. successTitle: t("Complete Success"),
  621. errorTitle: t("Complete Fail"),
  622. buttonText: t("Complete PO"),
  623. buttonIcon: <DoneIcon />,
  624. buttonColor: "info",
  625. disabled: true,
  626. };
  627. // break;
  628. }
  629. }, [purchaseOrder.status, t, handleStartPo, handleCompletePo]);
  630. const FIRST_IN_FIELD = "firstInQty"
  631. const SECOND_IN_FIELD = "secondInQty"
  632. const renderFieldCondition = useCallback((field: "firstInQty" | "secondInQty"): boolean => {
  633. switch (field) {
  634. case FIRST_IN_FIELD:
  635. return true;
  636. case SECOND_IN_FIELD:
  637. return true;
  638. default:
  639. return false; // Default case
  640. }
  641. }, []);
  642. useEffect(() => {
  643. const params = searchParams.get("polId")
  644. if (params) {
  645. const polId = parseInt(params)
  646. }
  647. }, [searchParams])
  648. const handleDatePickerChange = useCallback((value: Dayjs | null, onChange: (...event: any[]) => void) => {
  649. if (value != null) {
  650. const updatedValue = dayjsToDateString(value)
  651. onChange(updatedValue)
  652. } else {
  653. onChange(value)
  654. }
  655. }, [])
  656. return (
  657. <>
  658. <Stack spacing={2}>
  659. {/* Area1: title */}
  660. <Grid container xs={12} justifyContent="start">
  661. <Grid item>
  662. <Typography mb={2} variant="h4">
  663. {purchaseOrder.code} -{" "}
  664. {t(`${purchaseOrder.status.toLowerCase()}`)}
  665. </Typography>
  666. </Grid>
  667. </Grid>
  668. {/* area2: dn info */}
  669. <Grid container spacing={3} sx={{ maxWidth: 'fit-content' }}>
  670. {/* left side select po */}
  671. <Grid item xs={4}>
  672. <PoSearchList
  673. poList={poList}
  674. selectedPoId={selectedPoId}
  675. onSelect={handlePoSelect}
  676. />
  677. </Grid>
  678. {/* right side po info */}
  679. <Grid item xs={8}>
  680. <Grid container spacing={3} sx={{ maxWidth: 'fit-content' }}>
  681. <Grid item xs={12}>
  682. <PoInfoCard po={purchaseOrder} />
  683. </Grid>
  684. <Grid item xs={12}>
  685. {true ? (
  686. <FormProvider {...dnFormProps}>
  687. <Stack component={"form"} spacing={2}>
  688. <Card sx={{ display: "block", height: "230px" }}>
  689. <CardContent component={Stack} spacing={2}>
  690. <Grid container spacing={2} sx={{ maxWidth: 'fit-content' }}>
  691. <Grid item xs={12}>
  692. <TextField
  693. {...dnFormProps.register("dnNo")}
  694. label={t("dnNo")}
  695. type="text"
  696. variant="outlined"
  697. fullWidth
  698. // InputProps={{
  699. // inputProps: {
  700. // min: 0,
  701. // step: 1
  702. // }
  703. // }}
  704. />
  705. </Grid>
  706. <Grid item xs={12}>
  707. <LocalizationProvider
  708. dateAdapter={AdapterDayjs}
  709. // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
  710. adapterLocale="zh-hk"
  711. localeText={zhHK.components.MuiLocalizationProvider.defaultProps.localeText}
  712. >
  713. <Controller
  714. control={dnFormProps.control}
  715. name="receiptDate"
  716. render={({ field }) => (
  717. <DatePicker
  718. label={t("receiptDate")}
  719. format={`${OUTPUT_DATE_FORMAT}`}
  720. defaultValue={dateStringToDayjs(field.value)}
  721. onChange={(newValue: Dayjs | null) => {
  722. handleDatePickerChange(newValue, field.onChange)
  723. }}
  724. slotProps={{ textField: { fullWidth: true }}}
  725. />
  726. )}
  727. />
  728. </LocalizationProvider>
  729. </Grid>
  730. {/* <TextField
  731. label={t("dnDate")}
  732. type="text"
  733. variant="outlined"
  734. defaultValue={"11/08/2025"}
  735. fullWidth
  736. /> */}
  737. </Grid>
  738. {/* <Button variant="contained" onClick={onOpenScanner} fullWidth>
  739. 提交
  740. </Button> */}
  741. </CardContent>
  742. </Card>
  743. </Stack>
  744. </FormProvider>
  745. ) : undefined}
  746. </Grid>
  747. </Grid>
  748. </Grid>
  749. </Grid>
  750. {/* Area4: Main Table */}
  751. <Grid container xs={12} justifyContent="start">
  752. <Grid item xs={12}>
  753. <TableContainer component={Paper} sx={{ width: 'fit-content', overflow: 'auto' }}>
  754. <Table aria-label="collapsible table" stickyHeader>
  755. <TableHead>
  756. <TableRow>
  757. <TableCell align="center" sx={{ width: '60px' }}></TableCell>
  758. <TableCell sx={{ width: '125px' }}>{t("itemNo")}</TableCell>
  759. <TableCell align="left" sx={{ width: '125px' }}>{t("itemName")}</TableCell>
  760. <TableCell align="right">{t("qty")}</TableCell>
  761. <TableCell align="right">{t("processedQty")}</TableCell>
  762. <TableCell align="left">{t("uom")}</TableCell>
  763. <TableCell align="right">{t("receivedTotal")}</TableCell>
  764. <TableCell align="left">{t("Stock UoM")}</TableCell>
  765. {/* <TableCell align="right">{t("total weight")}</TableCell> */}
  766. {/* <TableCell align="right">{`${t("price")} (HKD)`}</TableCell> */}
  767. <TableCell align="left" sx={{ width: '75px' }}>{t("status")}</TableCell>
  768. {/* {renderFieldCondition(FIRST_IN_FIELD) ? <TableCell align="right">{t("receivedQty")}</TableCell> : undefined} */}
  769. <TableCell align="center" sx={{ width: '150px' }}>{t("productLotNo")}</TableCell>
  770. {renderFieldCondition(SECOND_IN_FIELD) ? <TableCell align="center" sx={{ width: '150px' }}>{t("dnQty")}<br/>(以訂單單位計算)</TableCell> : undefined}
  771. <TableCell align="center" sx={{ width: '100px' }}></TableCell>
  772. </TableRow>
  773. </TableHead>
  774. <TableBody>
  775. {rows.map((row) => (
  776. <Row key={row.id} row={row} />
  777. ))}
  778. </TableBody>
  779. </Table>
  780. </TableContainer>
  781. </Grid>
  782. </Grid>
  783. {/* area5: selected item info */}
  784. <Grid container xs={12} justifyContent="start">
  785. <Grid item xs={12}>
  786. <Typography variant="h6">
  787. {selectedRow ? `已選擇貨品: ${selectedRow?.itemNo ? selectedRow.itemNo : 'N/A'} - ${selectedRow?.itemName ? selectedRow?.itemName : 'N/A'}` : "未選擇貨品"}
  788. </Typography>
  789. </Grid>
  790. <Grid item xs={12}>
  791. {selectedRow && (
  792. <TableContainer component={Paper} sx={{ width: 'fit-content', overflow: 'auto' }}>
  793. <Table>
  794. <TableBody>
  795. <TableRow>
  796. <TableCell align="right">
  797. <Box>
  798. <PoInputGrid
  799. // qc={qc}
  800. setRows={setRows}
  801. stockInLine={stockInLine}
  802. setStockInLine={setStockInLine}
  803. setProcessedQty={setProcessedQty}
  804. itemDetail={selectedRow}
  805. warehouse={warehouse}
  806. fetchPoDetail={fetchPoDetail}
  807. handleMailTemplateForStockInLine={handleMailTemplateForStockInLine}
  808. printerCombo={printerCombo}
  809. />
  810. </Box>
  811. </TableCell>
  812. </TableRow>
  813. </TableBody>
  814. </Table>
  815. </TableContainer>
  816. )}
  817. </Grid>
  818. </Grid>
  819. {/* tab 2 */}
  820. <Grid sx={{ display: tabIndex === 1 ? "block" : "none" }}>
  821. {/* <StyledDataGrid
  822. /> */}
  823. </Grid>
  824. </Stack>
  825. {/* {itemInfo !== undefined && (
  826. <>
  827. <PoQcStockInModal
  828. type={"putaway"}
  829. open={putAwayOpen}
  830. warehouse={warehouse}
  831. setItemDetail={setItemInfo}
  832. onClose={closePutAwayModal}
  833. itemDetail={itemInfo}
  834. />
  835. </>
  836. )} */}
  837. </>
  838. );
  839. };
  840. export default PoDetail;