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

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