FPSMS-frontend
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 

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