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.
 
 

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