FPSMS-frontend
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 

306 righe
10 KiB

  1. "use client";
  2. import React, { useState, useEffect, useCallback } from "react";
  3. import { useSession } from "next-auth/react";
  4. import { SessionWithTokens } from "@/config/authConfig";
  5. import { Box, Tabs, Tab, Stack, Typography, Autocomplete, TextField } from "@mui/material";
  6. import { usePathname, useRouter, useSearchParams } from "next/navigation";
  7. import QcStockInModal from "@/components/Qc/QcStockInModal";
  8. import ProductionProcessList, {
  9. createDefaultProductionProcessListPersistedState,
  10. } from "@/components/ProductionProcess/ProductionProcessList";
  11. import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail";
  12. import ProductionProcessJobOrderDetail from "@/components/ProductionProcess/ProductionProcessJobOrderDetail";
  13. import JobPickExecutionsecondscan from "@/components/Jodetail/JobPickExecutionsecondscan";
  14. import JobProcessStatus from "@/components/ProductionProcess/JobProcessStatus";
  15. import OperatorKpiDashboard from "@/components/ProductionProcess/OperatorKpiDashboard";
  16. import EquipmentStatusDashboard from "@/components/ProductionProcess/EquipmentStatusDashboard";
  17. import type { PrinterCombo } from "@/app/api/settings/printer";
  18. import { useTranslation } from "react-i18next";
  19. interface ProductionProcessPageProps {
  20. printerCombo: PrinterCombo[];
  21. }
  22. const STORAGE_KEY = 'productionProcess_selectedMatchingStock';
  23. const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCombo }) => {
  24. const { t } = useTranslation(["common"]);
  25. const [selectedProcessId, setSelectedProcessId] = useState<number | null>(null);
  26. const [selectedMatchingStock, setSelectedMatchingStock] = useState<{
  27. jobOrderId: number;
  28. productProcessId: number;
  29. pickOrderId: number;
  30. } | null>(null);
  31. const [tabIndex, setTabIndex] = useState(0);
  32. /** 列表搜尋/分頁:保留在切換工單詳情時,返回後仍為同一條件 */
  33. const [productionListState, setProductionListState] = useState(() => ({
  34. ...createDefaultProductionProcessListPersistedState(),
  35. date: "",
  36. }));
  37. const [waitingPutawayListState, setWaitingPutawayListState] = useState(
  38. createDefaultProductionProcessListPersistedState,
  39. );
  40. const [putawayedListState, setPutawayedListState] = useState(
  41. createDefaultProductionProcessListPersistedState,
  42. );
  43. const { data: session } = useSession() as { data: SessionWithTokens | null };
  44. const sessionToken = session as SessionWithTokens | null;
  45. const searchParams = useSearchParams();
  46. const pathname = usePathname();
  47. const router = useRouter();
  48. const [linkQcOpen, setLinkQcOpen] = useState(false);
  49. const [linkQcSilId, setLinkQcSilId] = useState<number | null>(null);
  50. // Add printer selection state
  51. const [selectedPrinter, setSelectedPrinter] = useState<PrinterCombo | null>(
  52. printerCombo && printerCombo.length > 0 ? printerCombo[0] : null
  53. );
  54. // 从 sessionStorage 恢复状态(仅在客户端)
  55. useEffect(() => {
  56. if (typeof window !== 'undefined') {
  57. try {
  58. const saved = sessionStorage.getItem(STORAGE_KEY);
  59. if (saved) {
  60. const parsed = JSON.parse(saved);
  61. // 验证数据有效性
  62. if (parsed && typeof parsed.jobOrderId === 'number' && typeof parsed.productProcessId === 'number') {
  63. setSelectedMatchingStock(parsed);
  64. console.log(" Restored selectedMatchingStock from sessionStorage:", parsed);
  65. }
  66. }
  67. } catch (error) {
  68. console.error("Error restoring selectedMatchingStock:", error);
  69. sessionStorage.removeItem(STORAGE_KEY);
  70. }
  71. }
  72. }, []);
  73. // 保存状态到 sessionStorage
  74. useEffect(() => {
  75. if (typeof window !== 'undefined') {
  76. if (selectedMatchingStock) {
  77. sessionStorage.setItem(STORAGE_KEY, JSON.stringify(selectedMatchingStock));
  78. console.log(" Saved selectedMatchingStock to sessionStorage:", selectedMatchingStock);
  79. } else {
  80. sessionStorage.removeItem(STORAGE_KEY);
  81. }
  82. }
  83. }, [selectedMatchingStock]);
  84. // 处理返回列表时清除存储
  85. const handleBackFromSecondScan = useCallback(() => {
  86. setSelectedMatchingStock(null);
  87. if (typeof window !== 'undefined') {
  88. sessionStorage.removeItem(STORAGE_KEY);
  89. }
  90. }, []);
  91. const handleTabChange = useCallback((event: React.SyntheticEvent, newValue: number) => {
  92. setTabIndex(newValue);
  93. }, []);
  94. const openStockInLineIdQ = searchParams.get("openStockInLineId");
  95. /** Deep link from nav alert: /productionProcess?openStockInLineId=… → 「完成QC工單」tab + FG QC modal */
  96. useEffect(() => {
  97. if (!openStockInLineIdQ) {
  98. setLinkQcOpen(false);
  99. setLinkQcSilId(null);
  100. return;
  101. }
  102. const id = parseInt(openStockInLineIdQ, 10);
  103. if (!Number.isFinite(id) || id <= 0) return;
  104. setSelectedProcessId(null);
  105. setSelectedMatchingStock(null);
  106. setTabIndex(1);
  107. setLinkQcSilId(id);
  108. setLinkQcOpen(true);
  109. }, [openStockInLineIdQ]);
  110. const closeLinkQc = useCallback(() => {
  111. setLinkQcOpen(false);
  112. setLinkQcSilId(null);
  113. const p = new URLSearchParams(searchParams.toString());
  114. p.delete("openStockInLineId");
  115. const q = p.toString();
  116. router.replace(q ? `${pathname}?${q}` : pathname, { scroll: false });
  117. }, [pathname, router, searchParams]);
  118. if (selectedMatchingStock) {
  119. return (
  120. <JobPickExecutionsecondscan
  121. filterArgs={{
  122. jobOrderId: selectedMatchingStock.jobOrderId,
  123. pickOrderId: selectedMatchingStock.pickOrderId,
  124. }}
  125. onBack={handleBackFromSecondScan}
  126. />
  127. );
  128. }
  129. if (selectedProcessId !== null) {
  130. return (
  131. <ProductionProcessJobOrderDetail
  132. jobOrderId={selectedProcessId}
  133. initialTabIndex={2}
  134. onBack={() => setSelectedProcessId(null)}
  135. />
  136. );
  137. }
  138. return (
  139. <>
  140. <Box>
  141. {/* Header section with printer selection */}
  142. {tabIndex === 1 && (
  143. <Box sx={{
  144. p: 1,
  145. borderBottom: '1px solid #e0e0e0',
  146. minHeight: 'auto',
  147. display: 'flex',
  148. alignItems: 'center',
  149. justifyContent: 'flex-end',
  150. gap: 2,
  151. flexWrap: 'wrap',
  152. }}>
  153. <Stack
  154. direction="row"
  155. spacing={2}
  156. sx={{
  157. alignItems: 'center',
  158. flexWrap: 'wrap',
  159. rowGap: 1,
  160. }}
  161. >
  162. <Typography variant="body2" sx={{ minWidth: 'fit-content', mr: 1.5 }}>
  163. {t("Select Printer")}:
  164. </Typography>
  165. <Autocomplete
  166. disableClearable
  167. options={printerCombo || []}
  168. getOptionLabel={(option) =>
  169. option.name || option.label || option.code || `Printer ${option.id}`
  170. }
  171. value={selectedPrinter || undefined}
  172. onChange={(_, newValue) => setSelectedPrinter(newValue)}
  173. sx={{ minWidth: 200 }}
  174. size="small"
  175. renderInput={(params) => (
  176. <TextField
  177. {...params}
  178. placeholder={t("Printer")}
  179. inputProps={{
  180. ...params.inputProps,
  181. readOnly: true,
  182. }}
  183. />
  184. )}
  185. />
  186. </Stack>
  187. </Box>
  188. )}
  189. <Tabs value={tabIndex} onChange={handleTabChange} sx={{ mb: 2 }}>
  190. <Tab label={t("Production Process")} />
  191. <Tab label={t("Waiting QC Put Away Job Orders")} />
  192. <Tab label={t("Put Awayed Job Orders")} />
  193. <Tab label={t("Job Process Status Dashboard")} />
  194. <Tab label={t("Operator KPI Dashboard")} />
  195. <Tab label={t("Production Equipment Status Dashboard")} />
  196. </Tabs>
  197. {tabIndex === 0 && (
  198. <ProductionProcessList
  199. printerCombo={printerCombo}
  200. qcReady={false}
  201. disableDateFilter={false}
  202. listPersistedState={productionListState}
  203. onListPersistedStateChange={setProductionListState}
  204. onSelectProcess={(jobOrderId) => {
  205. const id = jobOrderId ?? null;
  206. if (id !== null) {
  207. setSelectedProcessId(id);
  208. }
  209. }}
  210. onSelectMatchingStock={(jobOrderId, productProcessId,pickOrderId) => {
  211. setSelectedMatchingStock({
  212. jobOrderId: jobOrderId || 0,
  213. productProcessId: productProcessId || 0 ,
  214. pickOrderId: pickOrderId || 0
  215. });
  216. }}
  217. />
  218. )}
  219. {tabIndex === 1 && (
  220. <ProductionProcessList
  221. printerCombo={printerCombo}
  222. qcReady={true}
  223. includePutaway={true}
  224. putawayStatus="notCompleted"
  225. listPersistedState={waitingPutawayListState}
  226. onListPersistedStateChange={setWaitingPutawayListState}
  227. onSelectProcess={(jobOrderId) => {
  228. const id = jobOrderId ?? null;
  229. if (id !== null) {
  230. setSelectedProcessId(id);
  231. }
  232. }}
  233. onSelectMatchingStock={(jobOrderId, productProcessId, pickOrderId) => {
  234. setSelectedMatchingStock({
  235. jobOrderId: jobOrderId || 0,
  236. productProcessId: productProcessId || 0,
  237. pickOrderId: pickOrderId || 0,
  238. });
  239. }}
  240. />
  241. )}
  242. {tabIndex === 2 && (
  243. <ProductionProcessList
  244. printerCombo={printerCombo}
  245. qcReady={true}
  246. includePutaway={true}
  247. putawayStatus="completed"
  248. listPersistedState={putawayedListState}
  249. onListPersistedStateChange={setPutawayedListState}
  250. onSelectProcess={(jobOrderId) => {
  251. const id = jobOrderId ?? null;
  252. if (id !== null) {
  253. setSelectedProcessId(id);
  254. }
  255. }}
  256. onSelectMatchingStock={(jobOrderId, productProcessId, pickOrderId) => {
  257. setSelectedMatchingStock({
  258. jobOrderId: jobOrderId || 0,
  259. productProcessId: productProcessId || 0,
  260. pickOrderId: pickOrderId || 0,
  261. });
  262. }}
  263. />
  264. )}
  265. {tabIndex === 3 && (
  266. <JobProcessStatus />
  267. )}
  268. {tabIndex === 4 && (
  269. <OperatorKpiDashboard />
  270. )}
  271. {tabIndex === 5 && (
  272. <EquipmentStatusDashboard />
  273. )}
  274. </Box>
  275. <QcStockInModal
  276. session={sessionToken}
  277. open={Boolean(linkQcOpen && linkQcSilId != null && linkQcSilId > 0)}
  278. onClose={closeLinkQc}
  279. inputDetail={linkQcSilId != null && linkQcSilId > 0 ? { id: linkQcSilId } : undefined}
  280. printerCombo={printerCombo}
  281. warehouse={[]}
  282. printSource="productionProcess"
  283. uiMode="default"
  284. />
  285. </>
  286. );
  287. };
  288. export default ProductionProcessPage;