FPSMS-frontend
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

FinishedGoodSearch.tsx 28 KiB

8ヶ月前
5ヶ月前
8ヶ月前
8ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
2週間前
7ヶ月前
8ヶ月前
5ヶ月前
8ヶ月前
8ヶ月前
7ヶ月前
8ヶ月前
7ヶ月前
8ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
7ヶ月前
7ヶ月前
2ヶ月前
2ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
2週間前
7ヶ月前
2週間前
2週間前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
8ヶ月前
8ヶ月前
7ヶ月前
8ヶ月前
8ヶ月前
8ヶ月前
8ヶ月前
6ヶ月前
8ヶ月前
6ヶ月前
6ヶ月前
8ヶ月前
5ヶ月前
5ヶ月前
3ヶ月前
5ヶ月前
5ヶ月前
3ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
3ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
3ヶ月前
5ヶ月前
6ヶ月前
8ヶ月前
5ヶ月前
8ヶ月前
7ヶ月前
7ヶ月前
6ヶ月前
8ヶ月前
7ヶ月前
8ヶ月前
2ヶ月前
8ヶ月前
7ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  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;