FPSMS-frontend
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

903 lignes
28 KiB

  1. "use client";
  2. import { PickOrderResult } from "@/app/api/pickOrder";
  3. import { useCallback, useEffect, useMemo, useState } from "react";
  4. import { useTranslation } from "react-i18next";
  5. import SearchBox, { Criterion } from "../SearchBox";
  6. import {
  7. flatten,
  8. intersectionWith,
  9. isEmpty,
  10. sortBy,
  11. uniqBy,
  12. upperCase,
  13. upperFirst,
  14. } from "lodash";
  15. import {
  16. arrayToDayjs,
  17. } from "@/app/utils/formatUtil";
  18. import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box, TextField } from "@mui/material";
  19. import PickOrders from "./FinishedGood";
  20. import ConsolidatedPickOrders from "./ConsolidatedPickOrders";
  21. import PickExecution from "./GoodPickExecution";
  22. import CreatePickOrderModal from "./CreatePickOrderModal";
  23. import NewCreateItem from "./newcreatitem";
  24. import AssignAndRelease from "./AssignAndRelease";
  25. import AssignTo from "./assignTo";
  26. import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions";
  27. import { fetchPickOrderClient, autoAssignAndReleasePickOrder, autoAssignAndReleasePickOrderByStore, fetchReleasedDoPickOrders } from "@/app/api/pickOrder/actions";
  28. import Jobcreatitem from "./Jobcreatitem";
  29. import { useSession } from "next-auth/react";
  30. import { SessionWithTokens } from "@/config/authConfig";
  31. //import PickExecutionDetail from "./GoodPickExecutiondetail";
  32. import GoodPickExecutionRecord from "./GoodPickExecutionRecord";
  33. import Swal from "sweetalert2";
  34. import { printDN, printDNLabels } from "@/app/api/do/actions";
  35. import { FGPickOrderResponse, fetchStoreLaneSummary, assignByLane,type StoreLaneSummary } from "@/app/api/pickOrder/actions";
  36. import FGPickOrderCard from "./FGPickOrderCard";
  37. import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
  38. import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
  39. import { DatePicker } from '@mui/x-date-pickers/DatePicker';
  40. import dayjs, { Dayjs } from 'dayjs';
  41. import { PrinterCombo } from "@/app/api/settings/printer";
  42. import { Autocomplete } from "@mui/material";
  43. import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable";
  44. import TruckRoutingSummaryTab, { TruckRoutingSummaryFilters } from "./TruckRoutingSummaryTab";
  45. import FinishedGoodCartonDashboardTab from "./FinishedGoodCartonDashboardTab";
  46. import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
  47. import { NEXT_PUBLIC_API_URL } from "@/config/api";
  48. import { fetchTruckRoutingSummaryPrecheck } from "@/app/(main)/report/truckRoutingSummaryApi";
  49. import {
  50. FEATURE_USAGE,
  51. FEATURE_USAGE_ACTION,
  52. logFeatureUsage,
  53. } from "@/lib/featureUsageLog";
  54. interface Props {
  55. // pickOrders: PickOrderResult[];
  56. printerCombo: PrinterCombo[];
  57. }
  58. type SearchQuery = Partial<
  59. Omit<PickOrderResult, "id" | "consoCode" | "completeDate">
  60. >;
  61. type SearchParamNames = keyof SearchQuery;
  62. const PickOrderSearch: React.FC<Props> = ({ printerCombo }) => {
  63. const { t } = useTranslation("pickOrder");
  64. const { data: session } = useSession() as { data: SessionWithTokens | null };
  65. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  66. const [isOpenCreateModal, setIsOpenCreateModal] = useState(false)
  67. const [items, setItems] = useState<ItemCombo[]>([])
  68. const [printButtonsEnabled, setPrintButtonsEnabled] = useState(false);
  69. //const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders);
  70. const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
  71. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  72. const [tabIndex, setTabIndex] = useState(0);
  73. const [totalCount, setTotalCount] = useState<number>();
  74. const [isAssigning, setIsAssigning] = useState(false);
  75. // const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null);
  76. // const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null);
  77. const [isLoadingSummary, setIsLoadingSummary] = useState(false);
  78. const [selectedPrinterForAllDraft, setSelectedPrinterForAllDraft] = useState<PrinterCombo | null>(null);
  79. const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCombo | null>(null);
  80. const [selectedPrinterForRecord, setSelectedPrinterForRecord] = useState<PrinterCombo | null>(null);
  81. const [truckRoutingFilters, setTruckRoutingFilters] = useState<TruckRoutingSummaryFilters>({
  82. storeId: "",
  83. truckLanceCode: "",
  84. date: "",
  85. });
  86. const [isPrintingRoutingSummary, setIsPrintingRoutingSummary] = useState(false);
  87. const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>(
  88. typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true'
  89. );
  90. const [fgPickOrdersData, setFgPickOrdersData] = useState<FGPickOrderResponse[]>([]);
  91. useEffect(() => {
  92. if (typeof window === "undefined") return;
  93. const key = "__FG_PAGE_READY_TIMER_STARTED__" as const;
  94. if ((window as any)[key]) {
  95. console.log("Timer '[FinishedGoodSearch] page ready' already started, skip.");
  96. return;
  97. }
  98. (window as any)[key] = true;
  99. console.time("[FinishedGoodSearch] page ready");
  100. }, []);
  101. const [releasedOrderCount, setReleasedOrderCount] = useState<number>(0);
  102. useEffect(() => {
  103. console.time("[FinishedGoodSearch] initial render");
  104. return () => {
  105. console.timeEnd("[FinishedGoodSearch] initial render");
  106. };
  107. }, []);
  108. const fetchReleasedOrderCount = useCallback(async () => {
  109. try {
  110. const releasedOrders = await fetchReleasedDoPickOrders();
  111. const validCount = releasedOrders.length;
  112. setReleasedOrderCount(validCount);
  113. } catch (error) {
  114. console.error("Error fetching released order count:", error);
  115. setReleasedOrderCount(0);
  116. }
  117. }, []);
  118. /*
  119. const loadSummaries = useCallback(async () => {
  120. setIsLoadingSummary(true);
  121. try {
  122. const [s2, s4] = await Promise.all([
  123. fetchStoreLaneSummary("2/F"),
  124. fetchStoreLaneSummary("4/F")
  125. ]);
  126. setSummary2F(s2);
  127. setSummary4F(s4);
  128. } catch (error) {
  129. console.error("Error loading summaries:", error);
  130. } finally {
  131. setIsLoadingSummary(false);
  132. }
  133. }, []);
  134. useEffect(() => {
  135. loadSummaries();
  136. // 每30秒刷新一次
  137. }, [loadSummaries]);
  138. */
  139. const handleDraft = useCallback(async () =>{
  140. try{
  141. if (fgPickOrdersData.length === 0) {
  142. console.error("No FG Pick order data available");
  143. Swal.fire({
  144. title: "",
  145. text: t("Please take one pick order before printing the draft."),
  146. icon: "info"
  147. })
  148. return;
  149. }
  150. if (!selectedPrinterForDraft) {
  151. Swal.fire({
  152. position: "bottom-end",
  153. icon: "warning",
  154. text: t("Please select a printer first"),
  155. showConfirmButton: false,
  156. timer: 1500
  157. });
  158. return;
  159. }
  160. const currentFgOrder = fgPickOrdersData[0];
  161. const printRequest = {
  162. printerId: selectedPrinterForDraft.id,
  163. printQty: 1,
  164. isDraft: true,
  165. numOfCarton: 0,
  166. doPickOrderId: currentFgOrder.doPickOrderId
  167. };
  168. console.log("Printing draft with request: ", printRequest);
  169. const response = await printDN(printRequest);
  170. console.log("Print Draft response: ", response);
  171. if(response.success){
  172. Swal.fire({
  173. position: "bottom-end",
  174. icon: "success",
  175. text: t("Printed Successfully."),
  176. showConfirmButton: false,
  177. timer: 1500
  178. });
  179. } else {
  180. console.error("Print failed: ", response.message);
  181. Swal.fire({
  182. title: "",
  183. text: t("Please take one pick order before printing the draft."),
  184. icon: "info"
  185. })
  186. }
  187. } catch(error){
  188. console.error("error: ", error)
  189. }
  190. },[t, fgPickOrdersData, selectedPrinterForDraft]);
  191. const handleAllDraft = useCallback(async () =>{
  192. try {
  193. const releasedOrders = await fetchReleasedDoPickOrders();
  194. console.log('fgPickOrdersData length:' + releasedOrders.length)
  195. if (!selectedPrinterForAllDraft) {
  196. Swal.fire({
  197. position: "bottom-end",
  198. icon: "warning",
  199. text: t("Please select a printer first"),
  200. showConfirmButton: false,
  201. timer: 1500
  202. });
  203. return;
  204. }
  205. if(releasedOrders.length === 0) {
  206. console.log("No released do_pick_order records found");
  207. Swal.fire({
  208. title: "",
  209. text: t("No released pick order records found."),
  210. icon: "info"
  211. })
  212. return;
  213. }
  214. console.log("Found released orders:", releasedOrders);
  215. const confirmResult = await Swal.fire({
  216. title: t("Batch Print"),
  217. text: t("Confirm print: (") + releasedOrders.length.toString() + t("piece(s))"),
  218. icon: "question",
  219. showCancelButton: true,
  220. confirmButtonText: t("Confirm"),
  221. cancelButtonText: t("Cancel"),
  222. confirmButtonColor: "#8dba00",
  223. cancelButtonColor: "#F04438"
  224. });
  225. if (!confirmResult.isConfirmed) {
  226. return;
  227. }
  228. Swal.fire({
  229. title: t("Printing..."),
  230. text: t("Please wait..."),
  231. allowOutsideClick: false,
  232. allowEscapeKey: false,
  233. didOpen: () => {
  234. Swal.showLoading();
  235. }
  236. });
  237. for (const order of releasedOrders) {
  238. const doPickOrderId = order.id
  239. console.log(`Processing order - DoPickOrder ID: ${doPickOrderId}, Ticket No: ${order.ticketNo}`);
  240. const printRequest = {
  241. printerId: selectedPrinterForAllDraft.id,
  242. printQty: 1,
  243. isDraft: true,
  244. numOfCarton: 0,
  245. doPickOrderId: doPickOrderId
  246. };
  247. console.log("Printing draft with request:", printRequest)
  248. const response = await printDN(printRequest);
  249. if(!response.success) {
  250. console.error(`Print failed for order ${order.ticketNo}:`, response.message);
  251. }
  252. }
  253. Swal.fire({
  254. position: "bottom-end",
  255. icon: "success",
  256. text: t("Printed Successfully."),
  257. showConfirmButton: false,
  258. timer: 1500
  259. });
  260. } catch(error){
  261. console.error("Error in handleAllDraft:",error);
  262. }
  263. },[t, fgPickOrdersData, selectedPrinterForAllDraft]);
  264. const handlePrintTruckRoutingSummary = useCallback(async () => {
  265. const { storeId, truckLanceCode, date } = truckRoutingFilters;
  266. if (!storeId || !truckLanceCode || !date) {
  267. Swal.fire({
  268. position: "bottom-end",
  269. icon: "warning",
  270. text: "請先選擇 2/F 或 4/F、車線和日期",
  271. showConfirmButton: false,
  272. timer: 1800,
  273. });
  274. return;
  275. }
  276. setIsPrintingRoutingSummary(true);
  277. try {
  278. if (!selectedPrinterForAllDraft) {
  279. Swal.fire({
  280. position: "bottom-end",
  281. icon: "warning",
  282. text: t("Please select a printer first"),
  283. showConfirmButton: false,
  284. timer: 1500
  285. });
  286. return;
  287. }
  288. const precheck = await fetchTruckRoutingSummaryPrecheck({
  289. storeId,
  290. truckLanceCode,
  291. date,
  292. });
  293. if (precheck.hasUnpickedOrders) {
  294. const confirmResult = await Swal.fire({
  295. title: "仍有未執拾的成品訂單",
  296. text: `此車線仍有 ${precheck.unpickedOrderCount} 張未執拾的成品訂單,是否仍要列印?`,
  297. icon: "warning",
  298. showCancelButton: true,
  299. confirmButtonText: "仍要列印",
  300. cancelButtonText: "取消",
  301. confirmButtonColor: "#8dba00",
  302. cancelButtonColor: "#F04438",
  303. });
  304. if (!confirmResult.isConfirmed) {
  305. return;
  306. }
  307. }
  308. const qs = new URLSearchParams({
  309. printerId: selectedPrinterForAllDraft.id.toString(),
  310. printQty: "1",
  311. storeId,
  312. truckLanceCode,
  313. date,
  314. }).toString();
  315. const url = `${NEXT_PUBLIC_API_URL}/truck-routing-summary/print-direct?${qs}`;
  316. const response = await clientAuthFetch(url, {
  317. method: "GET",
  318. });
  319. if (!response.ok) {
  320. const errorText = await response.text();
  321. throw new Error(`HTTP ${response.status}: ${errorText}`);
  322. }
  323. logFeatureUsage(
  324. FEATURE_USAGE.TRUCK_ROUTING_SUMMARY,
  325. FEATURE_USAGE_ACTION.PRINT,
  326. `direct:${storeId}:${truckLanceCode}:${date}`,
  327. );
  328. Swal.fire({
  329. position: "bottom-end",
  330. icon: "success",
  331. text: t("Printed Successfully."),
  332. showConfirmButton: false,
  333. timer: 1500
  334. });
  335. } catch (error) {
  336. console.error("Failed to print Truck Routing Summary", error);
  337. Swal.fire({
  338. icon: "error",
  339. text: "列印送貨路線摘要失敗,請稍後再試。",
  340. });
  341. } finally {
  342. setIsPrintingRoutingSummary(false);
  343. }
  344. }, [truckRoutingFilters, selectedPrinterForAllDraft, t]);
  345. const isTruckRoutingTab = tabIndex === 6;
  346. const canPrintTruckRoutingSummary = Boolean(
  347. truckRoutingFilters.storeId &&
  348. truckRoutingFilters.truckLanceCode &&
  349. truckRoutingFilters.date &&
  350. !isPrintingRoutingSummary,
  351. );
  352. useEffect(() => {
  353. fetchReleasedOrderCount();
  354. }, [fetchReleasedOrderCount]);
  355. useEffect(() => {
  356. if (tabIndex === 6) {
  357. logFeatureUsage(FEATURE_USAGE.TRUCK_ROUTING_SUMMARY, FEATURE_USAGE_ACTION.PAGE_VIEW);
  358. }
  359. }, [tabIndex]);
  360. useEffect(() => {
  361. const onAssigned = () => {
  362. localStorage.removeItem('hideCompletedUntilNext');
  363. setHideCompletedUntilNext(false);
  364. // loadSummaries();
  365. };
  366. window.addEventListener('pickOrderAssigned', onAssigned);
  367. return () => window.removeEventListener('pickOrderAssigned', onAssigned);
  368. }, []);
  369. // ... existing code ...
  370. useEffect(() => {
  371. const handleCompletionStatusChange = (event: CustomEvent) => {
  372. const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail;
  373. // ✅ 修复:根据标签页和事件来源决定是否更新打印按钮状态
  374. if (eventTabIndex === undefined || eventTabIndex === tabIndex) {
  375. setPrintButtonsEnabled(allLotsCompleted);
  376. console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted);
  377. }
  378. };
  379. window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  380. return () => {
  381. window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  382. };
  383. }, [tabIndex]); // ✅ 添加 tabIndex 依赖
  384. // ✅ 新增:处理标签页切换时的打印按钮状态重置
  385. useEffect(() => {
  386. // 当切换到成品提貨記錄標籤時,重置打印按钮状态
  387. if (tabIndex === 2 || tabIndex === 4) {
  388. setPrintButtonsEnabled(false);
  389. console.log("Reset print buttons for Pick Execution Record tab");
  390. }
  391. }, [tabIndex]);
  392. /*
  393. // ... existing code ...
  394. const handleAssignByLane = useCallback(async (
  395. storeId: string,
  396. truckDepartureTime: string,
  397. truckLanceCode: string
  398. ) => {
  399. if (!currentUserId) {
  400. console.error("Missing user id in session");
  401. return;
  402. }
  403. setIsAssigning(true);
  404. try {
  405. const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
  406. if (res.code === "SUCCESS") {
  407. console.log("✅ Successfully assigned pick order from lane", truckLanceCode);
  408. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  409. loadSummaries(); // 刷新按钮状态
  410. } else if (res.code === "USER_BUSY") {
  411. Swal.fire({
  412. icon: "warning",
  413. title: t("Warning"),
  414. text: t("You already have a pick order in progess. Please complete it first before taking next pick order."),
  415. confirmButtonText: t("Confirm"),
  416. confirmButtonColor: "#8dba00"
  417. });
  418. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  419. } else if (res.code === "NO_ORDERS") {
  420. Swal.fire({
  421. icon: "info",
  422. title: t("Info"),
  423. text: t("No available pick order(s) for this lane."),
  424. confirmButtonText: t("Confirm"),
  425. confirmButtonColor: "#8dba00"
  426. });
  427. } else {
  428. console.log("ℹ️ Assignment result:", res.message);
  429. }
  430. } catch (error) {
  431. console.error("❌ Error assigning by lane:", error);
  432. Swal.fire({
  433. icon: "error",
  434. title: t("Error"),
  435. text: t("Error occurred during assignment."),
  436. confirmButtonText: t("Confirm"),
  437. confirmButtonColor: "#8dba00"
  438. });
  439. } finally {
  440. setIsAssigning(false);
  441. }
  442. }, [currentUserId, t, loadSummaries]);
  443. // ✅ Manual assignment handler - uses the action function
  444. */
  445. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  446. (_e, newValue) => {
  447. setTabIndex(newValue);
  448. },
  449. [],
  450. );
  451. const handleSwitchToDetailTab = useCallback(() => {
  452. setTabIndex(1);
  453. }, []);
  454. const handleSwtitchToRecordTab = useCallback(() =>{
  455. setTabIndex(2);
  456. }, []);
  457. const openCreateModal = useCallback(async () => {
  458. console.log("testing")
  459. const res = await fetchAllItemsInClient()
  460. console.log(res)
  461. setItems(res)
  462. setIsOpenCreateModal(true)
  463. }, [])
  464. const closeCreateModal = useCallback(() => {
  465. setIsOpenCreateModal(false)
  466. }, [])
  467. useEffect(() => {
  468. if (tabIndex === 3) {
  469. const loadItems = async () => {
  470. try {
  471. const itemsData = await fetchAllItemsInClient();
  472. console.log("PickOrderSearch loaded items:", itemsData.length);
  473. setItems(itemsData);
  474. } catch (error) {
  475. console.error("Error loading items in PickOrderSearch:", error);
  476. }
  477. };
  478. // 如果还没有数据,则加载
  479. if (items.length === 0) {
  480. loadItems();
  481. }
  482. }
  483. }, [tabIndex, items.length]);
  484. useEffect(() => {
  485. const handleCompletionStatusChange = (event: CustomEvent) => {
  486. const { allLotsCompleted } = event.detail;
  487. setPrintButtonsEnabled(allLotsCompleted);
  488. console.log("Print buttons enabled:", allLotsCompleted);
  489. };
  490. window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  491. return () => {
  492. window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  493. };
  494. }, []);
  495. /*
  496. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  497. () => {
  498. const baseCriteria: Criterion<SearchParamNames>[] = [
  499. {
  500. label: tabIndex === 3 ? t("Item Code") : t("Code"),
  501. paramName: "code",
  502. type: "text"
  503. },
  504. {
  505. label: t("Type"),
  506. paramName: "type",
  507. type: "autocomplete",
  508. options: tabIndex === 3
  509. ?
  510. [
  511. { value: "Consumable", label: t("Consumable") },
  512. { value: "Material", label: t("Material") },
  513. { value: "Product", label: t("Product") }
  514. ]
  515. :
  516. sortBy(
  517. uniqBy(
  518. pickOrders.map((po) => ({
  519. value: po.type,
  520. label: t(upperCase(po.type)),
  521. })),
  522. "value",
  523. ),
  524. "label",
  525. ),
  526. },
  527. ];
  528. // Add Job Order search for Create Item tab (tabIndex === 3)
  529. if (tabIndex === 3) {
  530. baseCriteria.splice(1, 0, {
  531. label: t("Job Order"),
  532. paramName: "jobOrderCode" as any, // Type assertion for now
  533. type: "text",
  534. });
  535. baseCriteria.splice(2, 0, {
  536. label: t("Target Date"),
  537. paramName: "targetDate",
  538. type: "date",
  539. });
  540. } else {
  541. baseCriteria.splice(1, 0, {
  542. label: t("Target Date From"),
  543. label2: t("Target Date To"),
  544. paramName: "targetDate",
  545. type: "dateRange",
  546. });
  547. }
  548. // Add Items/Item Name criteria
  549. baseCriteria.push({
  550. label: tabIndex === 3 ? t("Item Name") : t("Items"),
  551. paramName: "items",
  552. type: tabIndex === 3 ? "text" : "autocomplete",
  553. options: tabIndex === 3
  554. ? []
  555. :
  556. uniqBy(
  557. flatten(
  558. sortBy(
  559. pickOrders.map((po) =>
  560. po.items
  561. ? po.items.map((item) => ({
  562. value: item.name,
  563. label: item.name,
  564. }))
  565. : [],
  566. ),
  567. "label",
  568. ),
  569. ),
  570. "value",
  571. ),
  572. });
  573. // Add Status criteria for non-Create Item tabs
  574. if (tabIndex !== 3) {
  575. baseCriteria.push({
  576. label: t("Status"),
  577. paramName: "status",
  578. type: "autocomplete",
  579. options: sortBy(
  580. uniqBy(
  581. pickOrders.map((po) => ({
  582. value: po.status,
  583. label: t(upperFirst(po.status)),
  584. })),
  585. "value",
  586. ),
  587. "label",
  588. ),
  589. });
  590. }
  591. return baseCriteria;
  592. },
  593. [pickOrders, t, tabIndex, items],
  594. );
  595. */
  596. /*
  597. const fetchNewPagePickOrder = useCallback(
  598. async (
  599. pagingController: Record<string, number>,
  600. filterArgs: Record<string, number>,
  601. ) => {
  602. const params = {
  603. ...pagingController,
  604. ...filterArgs,
  605. };
  606. const res = await fetchPickOrderClient(params);
  607. if (res) {
  608. console.log(res);
  609. setFilteredPickOrders(res.records);
  610. setTotalCount(res.total);
  611. }
  612. },
  613. [],
  614. );
  615. */
  616. /*
  617. const onReset = useCallback(() => {
  618. setFilteredPickOrders(pickOrders);
  619. }, [pickOrders]);
  620. */
  621. useEffect(() => {
  622. if (!isOpenCreateModal) {
  623. setTabIndex(1)
  624. setTimeout(async () => {
  625. setTabIndex(0)
  626. }, 200)
  627. }
  628. }, [isOpenCreateModal])
  629. // 添加处理提料单创建成功的函数
  630. const handlePickOrderCreated = useCallback(() => {
  631. // 切换到 Assign & Release 标签页 (tabIndex = 1)
  632. setTabIndex(2);
  633. }, []);
  634. return (
  635. <Box sx={{
  636. // Full viewport height
  637. overflow: 'auto' // Single scrollbar for the whole page
  638. }}>
  639. {/* Header section */}
  640. <Box
  641. sx={{
  642. p: 1,
  643. borderBottom: '1px solid #e0e0e0',
  644. minHeight: 'auto',
  645. display: 'flex',
  646. alignItems: 'center',
  647. justifyContent: 'space-between', // 左标题,右控件
  648. gap: 2,
  649. flexWrap: 'wrap', // 如果屏幕窄就自动换行
  650. }}
  651. >
  652. {/* 左侧标题 */}
  653. <Typography
  654. variant="h5"
  655. sx={{
  656. lineHeight: 1.4,
  657. m: 0,
  658. fontWeight: 500,
  659. }}
  660. >
  661. {t("Finished Good Order")}
  662. </Typography>
  663. {/* 右侧:打印机 + 按钮 */}
  664. <Stack
  665. direction="row"
  666. spacing={2}
  667. sx={{
  668. alignItems: 'center',
  669. flexWrap: 'wrap', // 控件太多时换行,不会撑出横向滚动
  670. rowGap: 1,
  671. }}
  672. >
  673. <Typography variant="body2" sx={{ minWidth: 'fit-content', mr: 1.5 }}>
  674. {t("A4 Printer")}:
  675. </Typography>
  676. <Autocomplete
  677. options={(printerCombo || []).filter(printer => printer.type === 'A4')}
  678. getOptionLabel={(option) =>
  679. option.name || option.label || option.code || `Printer ${option.id}`
  680. }
  681. value={selectedPrinterForAllDraft}
  682. onChange={(_, newValue) => setSelectedPrinterForAllDraft(newValue)}
  683. sx={{ minWidth: 200 }}
  684. size="small"
  685. renderInput={(params) => (
  686. <TextField {...params} placeholder={t("A4 Printer")}inputProps={{ ...params.inputProps, readOnly: true }} />
  687. )}
  688. />
  689. <Typography variant="body2" sx={{ minWidth: 'fit-content', ml: 1 }}>
  690. {t("Label Printer")}:
  691. </Typography>
  692. <Autocomplete
  693. options={(printerCombo || []).filter(printer => printer.type === 'Label')}
  694. getOptionLabel={(option) =>
  695. option.name || option.label || option.code || `Printer ${option.id}`
  696. }
  697. value={selectedPrinterForDraft}
  698. onChange={(_, newValue) => setSelectedPrinterForDraft(newValue)}
  699. sx={{ minWidth: 200 }}
  700. size="small"
  701. renderInput={(params) => (
  702. <TextField {...params} placeholder={t("Label Printer")} inputProps={{ ...params.inputProps, readOnly: true }}/>
  703. )}
  704. />
  705. <Button
  706. variant="contained"
  707. sx={{
  708. py: 0.5,
  709. px: 1.25,
  710. height: '40px',
  711. fontSize: '0.75rem',
  712. lineHeight: 1.2,
  713. display: 'flex',
  714. alignItems: 'center',
  715. justifyContent: 'center',
  716. '&.Mui-disabled': {
  717. height: '40px',
  718. },
  719. }}
  720. onClick={isTruckRoutingTab ? handlePrintTruckRoutingSummary : handleAllDraft}
  721. disabled={isTruckRoutingTab ? !canPrintTruckRoutingSummary : false}
  722. >
  723. {isTruckRoutingTab
  724. ? isPrintingRoutingSummary
  725. ? "生成中..."
  726. : "列印報告"
  727. : `${t("Print All Draft")} (${releasedOrderCount})`}
  728. </Button>
  729. {/*
  730. <Button
  731. variant="contained"
  732. sx={{
  733. py: 0.5,
  734. px: 1.25,
  735. height: '40px',
  736. fontSize: '0.75rem',
  737. lineHeight: 1.2,
  738. display: 'flex',
  739. alignItems: 'center',
  740. justifyContent: 'center',
  741. '&.Mui-disabled': {
  742. height: '40px',
  743. },
  744. }}
  745. title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
  746. onClick={handleDraft}
  747. >
  748. {t("Print Draft")}
  749. </Button>
  750. */}
  751. </Stack>
  752. </Box>
  753. {/* Tabs section - ✅ Move the click handler here */}
  754. <Box sx={{
  755. borderBottom: '1px solid #e0e0e0'
  756. }}>
  757. <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
  758. <Tab label={t("Pick Order Detail")} iconPosition="end" />
  759. <Tab label={t("Finished Good Detail")} iconPosition="end" />
  760. <Tab label={t("Finished Good Record")} iconPosition="end" />
  761. <Tab label={t("Ticket Release Table")} iconPosition="end" />
  762. <Tab label={t("Finished Good Record (All)")} iconPosition="end" />
  763. <Tab label="成品出倉出箱數量" iconPosition="end" />
  764. <Tab label="送貨路線摘要" iconPosition="end" />
  765. </Tabs>
  766. </Box>
  767. {/* Content section - NO overflow: 'auto' here */}
  768. <Box sx={{ p: 2 }}>
  769. {tabIndex === 0 && (
  770. <PickExecution
  771. filterArgs={filterArgs}
  772. onFgPickOrdersChange={setFgPickOrdersData}
  773. onSwitchToDetailTab={handleSwitchToDetailTab}
  774. onFirstLoadDone={() => {
  775. if (typeof window === "undefined") return;
  776. const key = "__FG_PAGE_READY_TIMER_STARTED__" as const;
  777. // 只在计时器真的存在时才调用 timeEnd,避免 "does not exist"
  778. if ((window as any)[key]) {
  779. console.timeEnd("[FinishedGoodSearch] page ready");
  780. delete (window as any)[key];
  781. } else {
  782. console.log("Timer '[FinishedGoodSearch] page ready' was already ended, skip.");
  783. }
  784. }}
  785. />
  786. )}
  787. {/*
  788. {tabIndex === 1 && (
  789. /*<PickExecutionDetail
  790. filterArgs={filterArgs}
  791. onSwitchToRecordTab={handleSwtitchToRecordTab}
  792. onRefreshReleasedOrderCount={fetchReleasedOrderCount}
  793. />
  794. ) */}
  795. {tabIndex === 2 && (
  796. <GoodPickExecutionRecord
  797. filterArgs={filterArgs}
  798. printerCombo={printerCombo}
  799. a4Printer={selectedPrinterForAllDraft}
  800. labelPrinter={selectedPrinterForDraft}
  801. recordTabIndex={2}
  802. listScope="mine"
  803. />
  804. )}
  805. {tabIndex === 3 && <FGPickOrderTicketReleaseTable />}
  806. {tabIndex === 4 && (
  807. <GoodPickExecutionRecord
  808. filterArgs={filterArgs}
  809. printerCombo={printerCombo}
  810. a4Printer={selectedPrinterForAllDraft}
  811. labelPrinter={selectedPrinterForDraft}
  812. recordTabIndex={4}
  813. listScope="all"
  814. />
  815. )}
  816. {tabIndex === 5 && (
  817. <FinishedGoodCartonDashboardTab />
  818. )}
  819. {tabIndex === 6 && (
  820. <TruckRoutingSummaryTab onFiltersChange={setTruckRoutingFilters} />
  821. )}
  822. </Box>
  823. </Box>
  824. );
  825. };
  826. export default PickOrderSearch;