FPSMS-frontend
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 

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