FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FinishedGoodSearch.tsx 28 KiB

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