FPSMS-frontend
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

496 lines
16 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 } from "@mui/material";
  19. import Jodetail from "./Jodetail"
  20. import PickExecution from "./JobPickExecution";
  21. import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions";
  22. import { fetchPickOrderClient, autoAssignAndReleasePickOrder, autoAssignAndReleasePickOrderByStore } from "@/app/api/pickOrder/actions";
  23. import { useSession } from "next-auth/react";
  24. import { SessionWithTokens } from "@/config/authConfig";
  25. import JobPickExecutionsecondscan from "./JobPickExecutionsecondscan";
  26. import FInishedJobOrderRecord from "./FInishedJobOrderRecord";
  27. import JobPickExecution from "./JobPickExecution";
  28. import CompleteJobOrderRecord from "./completeJobOrderRecord";
  29. import {
  30. fetchUnassignedJobOrderPickOrders,
  31. assignJobOrderPickOrder,
  32. fetchJobOrderLotsHierarchical,
  33. fetchCompletedJobOrderPickOrders,
  34. fetchCompletedJobOrderPickOrderRecords
  35. } from "@/app/api/jo/actions";
  36. import { fetchPrinterCombo } from "@/app/api/settings/printer";
  37. import { PrinterCombo } from "@/app/api/settings/printer";
  38. interface Props {
  39. pickOrders: PickOrderResult[];
  40. printerCombo: PrinterCombo[];
  41. }
  42. type SearchQuery = Partial<
  43. Omit<PickOrderResult, "id" | "consoCode" | "completeDate">
  44. >;
  45. type SearchParamNames = keyof SearchQuery;
  46. const JodetailSearch: React.FC<Props> = ({ pickOrders, printerCombo }) => {
  47. const { t } = useTranslation("jo");
  48. const { data: session } = useSession() as { data: SessionWithTokens | null };
  49. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  50. const [isOpenCreateModal, setIsOpenCreateModal] = useState(false)
  51. const [items, setItems] = useState<ItemCombo[]>([])
  52. const [printButtonsEnabled, setPrintButtonsEnabled] = useState(false);
  53. const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders);
  54. const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
  55. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  56. const [tabIndex, setTabIndex] = useState(0);
  57. const [totalCount, setTotalCount] = useState<number>();
  58. const [isAssigning, setIsAssigning] = useState(false);
  59. const [unassignedOrders, setUnassignedOrders] = useState<any[]>([]);
  60. const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false);
  61. const [hasAssignedJobOrders, setHasAssignedJobOrders] = useState(false);
  62. const [hasDataTab0, setHasDataTab0] = useState(false);
  63. const [hasDataTab1, setHasDataTab1] = useState(false);
  64. const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
  65. //const [printers, setPrinters] = useState<PrinterCombo[]>([]);
  66. const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>(
  67. typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true'
  68. );
  69. useEffect(() => {
  70. const onJobOrderDataStatus = (e: CustomEvent) => {
  71. const { hasData, tabIndex: idx } = e.detail || {};
  72. if (idx === 0) setHasDataTab0(!!hasData);
  73. if (idx === 1) setHasDataTab1(!!hasData);
  74. };
  75. window.addEventListener('jobOrderDataStatus', onJobOrderDataStatus as EventListener);
  76. return () => window.removeEventListener('jobOrderDataStatus', onJobOrderDataStatus as EventListener);
  77. }, []);
  78. useEffect(() => {
  79. const handleJobOrderDataChange = (event: CustomEvent) => {
  80. const { hasData, tabIndex: eventTabIndex } = event.detail;
  81. // Update the state based on which tab has data
  82. if (eventTabIndex === 0 || eventTabIndex === 1) {
  83. setHasAssignedJobOrders(hasData);
  84. console.log(`Job order data status for tab ${eventTabIndex}:`, hasData);
  85. }
  86. };
  87. window.addEventListener('jobOrderDataStatus', handleJobOrderDataChange as EventListener);
  88. return () => {
  89. window.removeEventListener('jobOrderDataStatus', handleJobOrderDataChange as EventListener);
  90. };
  91. }, []);
  92. /*
  93. useEffect(() => {
  94. const fetchPrinters = async () => {
  95. try {
  96. // 需要创建一个客户端版本的 fetchPrinterCombo
  97. // 或者使用 API 路由
  98. // const printersData = await fetch('/api/printers/combo').then(r => r.json());
  99. // setPrinters(printersData);
  100. } catch (error) {
  101. console.error("Error fetching printers:", error);
  102. }
  103. };
  104. fetchPrinters();
  105. }, []);
  106. */
  107. useEffect(() => {
  108. const onAssigned = () => {
  109. localStorage.removeItem('hideCompletedUntilNext');
  110. setHideCompletedUntilNext(false);
  111. };
  112. window.addEventListener('pickOrderAssigned', onAssigned);
  113. return () => window.removeEventListener('pickOrderAssigned', onAssigned);
  114. }, []);
  115. // ... existing code ...
  116. useEffect(() => {
  117. const handleCompletionStatusChange = (event: CustomEvent) => {
  118. const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail;
  119. // 修复:根据标签页和事件来源决定是否更新打印按钮状态
  120. if (eventTabIndex === undefined || eventTabIndex === tabIndex) {
  121. setPrintButtonsEnabled(allLotsCompleted);
  122. console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted);
  123. }
  124. };
  125. window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  126. return () => {
  127. window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  128. };
  129. }, [tabIndex]); // 添加 tabIndex 依赖
  130. // 新增:处理标签页切换时的打印按钮状态重置
  131. useEffect(() => {
  132. // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态
  133. if (tabIndex === 2) {
  134. setPrintButtonsEnabled(false);
  135. console.log("Reset print buttons for Pick Execution Record tab");
  136. }
  137. }, [tabIndex]);
  138. // ... existing code ...
  139. const handleAssignByStore = async (storeId: "2/F" | "4/F") => {
  140. if (!currentUserId) {
  141. console.error("Missing user id in session");
  142. return;
  143. }
  144. setIsAssigning(true);
  145. try {
  146. const res = await autoAssignAndReleasePickOrderByStore(currentUserId, storeId);
  147. console.log("Assign by store result:", res);
  148. // Handle different response codes
  149. if (res.code === "SUCCESS") {
  150. console.log(" Successfully assigned pick order to store", storeId);
  151. // Trigger refresh to show newly assigned data
  152. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  153. } else if (res.code === "USER_BUSY") {
  154. console.warn("⚠️ User already has pick orders in progress:", res.message);
  155. // Show warning but still refresh to show existing orders
  156. alert(`Warning: ${res.message}`);
  157. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  158. } else if (res.code === "NO_ORDERS") {
  159. console.log("ℹ️ No available pick orders for store", storeId);
  160. alert(`Info: ${res.message}`);
  161. } else {
  162. console.log("ℹ️ Assignment result:", res.message);
  163. alert(`Info: ${res.message}`);
  164. }
  165. } catch (error) {
  166. console.error("❌ Error assigning by store:", error);
  167. alert("Error occurred during assignment");
  168. } finally {
  169. setIsAssigning(false);
  170. }
  171. };
  172. // Manual assignment handler - uses the action function
  173. const loadUnassignedOrders = useCallback(async () => {
  174. setIsLoadingUnassigned(true);
  175. try {
  176. const orders = await fetchUnassignedJobOrderPickOrders();
  177. setUnassignedOrders(orders);
  178. } catch (error) {
  179. console.error("Error loading unassigned orders:", error);
  180. setUnassignedOrders([]);
  181. } finally {
  182. setIsLoadingUnassigned(false);
  183. }
  184. }, []);
  185. // 分配订单给当前用户
  186. const handleAssignOrder = useCallback(async (pickOrderId: number) => {
  187. if (!currentUserId) {
  188. console.error("Missing user id in session");
  189. return;
  190. }
  191. try {
  192. const result = await assignJobOrderPickOrder(pickOrderId, currentUserId);
  193. if (result.message === "Successfully assigned") {
  194. console.log(" Successfully assigned pick order");
  195. // 刷新数据
  196. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  197. // 重新加载未分配订单列表
  198. loadUnassignedOrders();
  199. } else {
  200. console.warn("⚠️ Assignment failed:", result.message);
  201. alert(`Assignment failed: ${result.message}`);
  202. }
  203. } catch (error) {
  204. console.error("❌ Error assigning order:", error);
  205. alert("Error occurred during assignment");
  206. }
  207. }, [currentUserId, loadUnassignedOrders]);
  208. // 在组件加载时获取未分配订单
  209. useEffect(() => {
  210. loadUnassignedOrders();
  211. }, [loadUnassignedOrders]);
  212. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  213. (_e, newValue) => {
  214. setTabIndex(newValue);
  215. },
  216. [],
  217. );
  218. const openCreateModal = useCallback(async () => {
  219. console.log("testing")
  220. const res = await fetchAllItemsInClient()
  221. console.log(res)
  222. setItems(res)
  223. setIsOpenCreateModal(true)
  224. }, [])
  225. const closeCreateModal = useCallback(() => {
  226. setIsOpenCreateModal(false)
  227. }, [])
  228. useEffect(() => {
  229. if (tabIndex === 3) {
  230. const loadItems = async () => {
  231. try {
  232. const itemsData = await fetchAllItemsInClient();
  233. console.log("PickOrderSearch loaded items:", itemsData.length);
  234. setItems(itemsData);
  235. } catch (error) {
  236. console.error("Error loading items in PickOrderSearch:", error);
  237. }
  238. };
  239. // 如果还没有数据,则加载
  240. if (items.length === 0) {
  241. loadItems();
  242. }
  243. }
  244. }, [tabIndex, items.length]);
  245. useEffect(() => {
  246. const handleCompletionStatusChange = (event: CustomEvent) => {
  247. const { allLotsCompleted } = event.detail;
  248. setPrintButtonsEnabled(allLotsCompleted);
  249. console.log("Print buttons enabled:", allLotsCompleted);
  250. };
  251. window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  252. return () => {
  253. window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  254. };
  255. }, []);
  256. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  257. () => {
  258. const baseCriteria: Criterion<SearchParamNames>[] = [
  259. {
  260. label: tabIndex === 3 ? t("Item Code") : t("Code"),
  261. paramName: "code",
  262. type: "text"
  263. },
  264. {
  265. label: t("Type"),
  266. paramName: "type",
  267. type: "autocomplete",
  268. options: tabIndex === 3
  269. ?
  270. [
  271. { value: "Consumable", label: t("Consumable") },
  272. { value: "Material", label: t("Material") },
  273. { value: "Product", label: t("Product") }
  274. ]
  275. :
  276. sortBy(
  277. uniqBy(
  278. pickOrders.map((po) => ({
  279. value: po.type,
  280. label: t(upperCase(po.type)),
  281. })),
  282. "value",
  283. ),
  284. "label",
  285. ),
  286. },
  287. ];
  288. // Add Job Order search for Create Item tab (tabIndex === 3)
  289. if (tabIndex === 3) {
  290. baseCriteria.splice(1, 0, {
  291. label: t("Job Order"),
  292. paramName: "jobOrderCode" as any, // Type assertion for now
  293. type: "text",
  294. });
  295. baseCriteria.splice(2, 0, {
  296. label: t("Target Date"),
  297. paramName: "targetDate",
  298. type: "date",
  299. });
  300. } else {
  301. baseCriteria.splice(1, 0, {
  302. label: t("Target Date From"),
  303. label2: t("Target Date To"),
  304. paramName: "targetDate",
  305. type: "dateRange",
  306. });
  307. }
  308. // Add Items/Item Name criteria
  309. baseCriteria.push({
  310. label: tabIndex === 3 ? t("Item Name") : t("Items"),
  311. paramName: "items",
  312. type: tabIndex === 3 ? "text" : "autocomplete",
  313. options: tabIndex === 3
  314. ? []
  315. :
  316. uniqBy(
  317. flatten(
  318. sortBy(
  319. pickOrders.map((po) =>
  320. po.items
  321. ? po.items.map((item) => ({
  322. value: item.name,
  323. label: item.name,
  324. }))
  325. : [],
  326. ),
  327. "label",
  328. ),
  329. ),
  330. "value",
  331. ),
  332. });
  333. // Add Status criteria for non-Create Item tabs
  334. if (tabIndex !== 3) {
  335. baseCriteria.push({
  336. label: t("Status"),
  337. paramName: "status",
  338. type: "autocomplete",
  339. options: sortBy(
  340. uniqBy(
  341. pickOrders.map((po) => ({
  342. value: po.status,
  343. label: t(upperFirst(po.status)),
  344. })),
  345. "value",
  346. ),
  347. "label",
  348. ),
  349. });
  350. }
  351. return baseCriteria;
  352. },
  353. [pickOrders, t, tabIndex, items],
  354. );
  355. const fetchNewPagePickOrder = useCallback(
  356. async (
  357. pagingController: Record<string, number>,
  358. filterArgs: Record<string, number>,
  359. ) => {
  360. const params = {
  361. ...pagingController,
  362. ...filterArgs,
  363. };
  364. const res = await fetchPickOrderClient(params);
  365. if (res) {
  366. console.log(res);
  367. setFilteredPickOrders(res.records);
  368. setTotalCount(res.total);
  369. }
  370. },
  371. [],
  372. );
  373. const onReset = useCallback(() => {
  374. setFilteredPickOrders(pickOrders);
  375. }, [pickOrders]);
  376. useEffect(() => {
  377. if (!isOpenCreateModal) {
  378. setTabIndex(1)
  379. setTimeout(async () => {
  380. setTabIndex(0)
  381. }, 200)
  382. }
  383. }, [isOpenCreateModal])
  384. // 添加处理提料单创建成功的函数
  385. const handlePickOrderCreated = useCallback(() => {
  386. // 切换到 Assign & Release 标签页 (tabIndex = 1)
  387. setTabIndex(2);
  388. }, []);
  389. return (
  390. <Box sx={{
  391. height: '100vh', // Full viewport height
  392. overflow: 'auto' // Single scrollbar for the whole page
  393. }}>
  394. {/* Header section */}
  395. <Box sx={{ p: 2, borderBottom: '1px solid #e0e0e0' }}>
  396. <Stack rowGap={2}>
  397. <Grid container alignItems="center">
  398. <Grid item xs={8}>
  399. </Grid>
  400. {/* Last 2 buttons aligned right */}
  401. <Grid item xs={6} >
  402. {/* Unassigned Job Orders */}
  403. {!hasAnyAssignedData && unassignedOrders && unassignedOrders.length > 0 && (
  404. <Box sx={{ mt: 2, p: 2, border: '1px solid #e0e0e0', borderRadius: 1 }}>
  405. <Typography variant="h6" gutterBottom>
  406. {t("Unassigned Job Orders")} ({unassignedOrders.length})
  407. </Typography>
  408. <Stack direction="row" spacing={1} flexWrap="wrap">
  409. {unassignedOrders.map((order) => (
  410. <Button
  411. key={order.pickOrderId}
  412. variant="outlined"
  413. size="small"
  414. onClick={() => handleAssignOrder(order.pickOrderId)}
  415. disabled={isLoadingUnassigned}
  416. >
  417. {order.pickOrderCode} - {order.jobOrderName}
  418. </Button>
  419. ))}
  420. </Stack>
  421. </Box>
  422. )}
  423. </Grid>
  424. </Grid>
  425. </Stack>
  426. </Box>
  427. {/* Tabs section - Move the click handler here */}
  428. <Box sx={{
  429. borderBottom: '1px solid #e0e0e0'
  430. }}>
  431. <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
  432. <Tab label={t("Pick Order Detail")} iconPosition="end" />
  433. <Tab label={t("Complete Job Order Record")} iconPosition="end" />
  434. <Tab label={t("Job Order Match")} iconPosition="end" />
  435. {/* <Tab label={t("Finished Job Order Record")} iconPosition="end" /> */}
  436. </Tabs>
  437. </Box>
  438. {/* Content section - NO overflow: 'auto' here */}
  439. <Box sx={{
  440. p: 2
  441. }}>
  442. {tabIndex === 0 && <JobPickExecution filterArgs={filterArgs} />}
  443. {tabIndex === 1 && <CompleteJobOrderRecord filterArgs={filterArgs} printerCombo={printerCombo} />}
  444. {tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />}
  445. {/* {tabIndex === 3 && <FInishedJobOrderRecord filterArgs={filterArgs} />} */}
  446. </Box>
  447. </Box>
  448. );
  449. };
  450. export default JodetailSearch;