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.

476 lines
21 KiB

  1. "use client";
  2. import React, { useState } from "react";
  3. import {
  4. Box, Grid, Paper, Typography, Button, Dialog, DialogTitle,
  5. DialogContent, DialogActions, TextField, Stack, Table,
  6. TableBody, TableCell, TableContainer, TableHead, TableRow,
  7. Tabs, Tab // ← Added for tabs
  8. } from "@mui/material";
  9. import { FileDownload, Print, SettingsEthernet, Lan, Router } from "@mui/icons-material";
  10. import dayjs from "dayjs";
  11. import { NEXT_PUBLIC_API_URL } from "@/config/api";
  12. // Simple TabPanel component for conditional rendering
  13. interface TabPanelProps {
  14. children?: React.ReactNode;
  15. index: number;
  16. value: number;
  17. }
  18. function TabPanel(props: TabPanelProps) {
  19. const { children, value, index, ...other } = props;
  20. return (
  21. <div
  22. role="tabpanel"
  23. hidden={value !== index}
  24. id={`simple-tabpanel-${index}`}
  25. aria-labelledby={`simple-tab-${index}`}
  26. {...other}
  27. >
  28. {value === index && (
  29. <Box sx={{ p: 3 }}>
  30. {children}
  31. </Box>
  32. )}
  33. </div>
  34. );
  35. }
  36. export default function TestingPage() {
  37. // Tab state
  38. const [tabValue, setTabValue] = useState(0);
  39. const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
  40. setTabValue(newValue);
  41. };
  42. // --- 1. TSC Section States ---
  43. const [tscConfig, setTscConfig] = useState({ ip: '192.168.1.100', port: '9100' });
  44. const [tscItems, setTscItems] = useState([
  45. { id: 1, itemCode: 'FG-001', itemName: 'Yellow Curry Sauce', lotNo: 'LOT-TSC-01', expiryDate: '2025-12-01' },
  46. { id: 2, itemCode: 'FG-002', itemName: 'Red Curry Paste', lotNo: 'LOT-TSC-02', expiryDate: '2025-12-05' },
  47. ]);
  48. // --- 2. DataFlex Section States ---
  49. const [dfConfig, setDfConfig] = useState({ ip: '192.168.1.101', port: '9100' });
  50. const [dfItems, setDfItems] = useState([
  51. { id: 1, itemCode: 'DF-101', itemName: 'Instant Noodle A', lotNo: 'LOT-DF-01', expiryDate: '2026-01-10' },
  52. { id: 2, itemCode: 'DF-102', itemName: 'Instant Noodle B', lotNo: 'LOT-DF-02', expiryDate: '2026-01-15' },
  53. ]);
  54. // --- 3. OnPack Section States ---
  55. const [isPrinterModalOpen, setIsPrinterModalOpen] = useState(false);
  56. const [printerFormData, setPrinterFormData] = useState({
  57. itemCode: '',
  58. lotNo: '',
  59. expiryDate: dayjs().format('YYYY-MM-DD'),
  60. productName: ''
  61. });
  62. // --- 4. Laser Section States ---
  63. const [laserConfig, setLaserConfig] = useState({ ip: '192.168.1.102', port: '8080' });
  64. const [laserItems, setLaserItems] = useState([
  65. { id: 1, templateId: 'JOB_001', lotNo: 'L-LASER-01', expiryDate: '2025-12-31', power: '50' },
  66. ]);
  67. // --- 5. HANS600S-M Section States ---
  68. const [hansConfig, setHansConfig] = useState({ ip: '192.168.76.10', port: '45678' });
  69. const [hansItems, setHansItems] = useState([
  70. {
  71. id: 1,
  72. textChannel3: 'SN-HANS-001-20260117', // channel 3 (e.g. serial / text1)
  73. textChannel4: 'BATCH-HK-TEST-OK', // channel 4 (e.g. batch / text2)
  74. text3ObjectName: 'Text3', // EZCAD object name for channel 3
  75. text4ObjectName: 'Text4' // EZCAD object name for channel 4
  76. },
  77. ]);
  78. // Generic handler for inline table edits
  79. const handleItemChange = (setter: any, id: number, field: string, value: string) => {
  80. setter((prev: any[]) => prev.map(item =>
  81. item.id === id ? { ...item, [field]: value } : item
  82. ));
  83. };
  84. // --- API CALLS ---
  85. // TSC Print (Section 1)
  86. const handleTscPrint = async (row: any) => {
  87. const token = localStorage.getItem("accessToken");
  88. const payload = { ...row, printerIp: tscConfig.ip, printerPort: tscConfig.port };
  89. try {
  90. const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-tsc`, {
  91. method: 'POST',
  92. headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  93. body: JSON.stringify(payload)
  94. });
  95. if (response.ok) alert(`TSC Print Command Sent for ${row.itemCode}!`);
  96. else alert("TSC Print Failed");
  97. } catch (e) { console.error("TSC Error:", e); }
  98. };
  99. // DataFlex Print (Section 2)
  100. const handleDfPrint = async (row: any) => {
  101. const token = localStorage.getItem("accessToken");
  102. const payload = { ...row, printerIp: dfConfig.ip, printerPort: dfConfig.port };
  103. try {
  104. const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-dataflex`, {
  105. method: 'POST',
  106. headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  107. body: JSON.stringify(payload)
  108. });
  109. if (response.ok) alert(`DataFlex Print Command Sent for ${row.itemCode}!`);
  110. else alert("DataFlex Print Failed");
  111. } catch (e) { console.error("DataFlex Error:", e); }
  112. };
  113. // OnPack Zip Download (Section 3)
  114. const handleDownloadPrintJob = async () => {
  115. const token = localStorage.getItem("accessToken");
  116. const params = new URLSearchParams(printerFormData);
  117. try {
  118. const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/get-printer6?${params.toString()}`, {
  119. method: 'GET',
  120. headers: { 'Authorization': `Bearer ${token}` }
  121. });
  122. if (!response.ok) throw new Error('Download failed');
  123. const blob = await response.blob();
  124. const url = window.URL.createObjectURL(blob);
  125. const link = document.createElement('a');
  126. link.href = url;
  127. link.setAttribute('download', `${printerFormData.lotNo || 'OnPack'}.zip`);
  128. document.body.appendChild(link);
  129. link.click();
  130. link.remove();
  131. window.URL.revokeObjectURL(url);
  132. setIsPrinterModalOpen(false);
  133. } catch (e) { console.error("OnPack Error:", e); }
  134. };
  135. // Laser Print (Section 4 - original)
  136. const handleLaserPrint = async (row: any) => {
  137. const token = localStorage.getItem("accessToken");
  138. const payload = { ...row, printerIp: laserConfig.ip, printerPort: laserConfig.port };
  139. try {
  140. const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser`, {
  141. method: 'POST',
  142. headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  143. body: JSON.stringify(payload)
  144. });
  145. if (response.ok) alert(`Laser Command Sent: ${row.templateId}`);
  146. } catch (e) { console.error(e); }
  147. };
  148. const handleLaserPreview = async (row: any) => {
  149. const token = localStorage.getItem("accessToken");
  150. const payload = { ...row, printerIp: laserConfig.ip, printerPort: parseInt(laserConfig.port) };
  151. try {
  152. const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/preview-laser`, {
  153. method: 'POST',
  154. headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  155. body: JSON.stringify(payload)
  156. });
  157. if (response.ok) alert("Red light preview active!");
  158. } catch (e) { console.error("Preview Error:", e); }
  159. };
  160. // HANS600S-M TCP Print (Section 5)
  161. const handleHansPrint = async (row: any) => {
  162. const token = localStorage.getItem("accessToken");
  163. const payload = {
  164. printerIp: hansConfig.ip,
  165. printerPort: hansConfig.port,
  166. textChannel3: row.textChannel3,
  167. textChannel4: row.textChannel4,
  168. text3ObjectName: row.text3ObjectName,
  169. text4ObjectName: row.text4ObjectName
  170. };
  171. try {
  172. const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser-tcp`, {
  173. method: 'POST',
  174. headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  175. body: JSON.stringify(payload)
  176. });
  177. const result = await response.text();
  178. if (response.ok) {
  179. alert(`HANS600S-M Mark Success: ${result}`);
  180. } else {
  181. alert(`HANS600S-M Failed: ${result}`);
  182. }
  183. } catch (e) {
  184. console.error("HANS600S-M Error:", e);
  185. alert("HANS600S-M Connection Error");
  186. }
  187. };
  188. // Layout Helper
  189. const Section = ({ title, children }: { title: string, children?: React.ReactNode }) => (
  190. <Paper sx={{ p: 3, minHeight: '450px', display: 'flex', flexDirection: 'column' }}>
  191. <Typography variant="h5" gutterBottom color="primary" sx={{ borderBottom: '2px solid #f0f0f0', pb: 1, mb: 2 }}>
  192. {title}
  193. </Typography>
  194. {children || <Typography color="textSecondary" sx={{ m: 'auto' }}>Waiting for implementation...</Typography>}
  195. </Paper>
  196. );
  197. return (
  198. <Box sx={{ p: 4 }}>
  199. <Typography variant="h4" sx={{ mb: 4, fontWeight: 'bold' }}>Printer Testing</Typography>
  200. <Tabs value={tabValue} onChange={handleTabChange} aria-label="printer sections tabs" centered variant="fullWidth">
  201. <Tab label="1. TSC" />
  202. <Tab label="2. DataFlex" />
  203. <Tab label="3. OnPack" />
  204. <Tab label="4. Laser" />
  205. <Tab label="5. HANS600S-M" />
  206. </Tabs>
  207. <TabPanel value={tabValue} index={0}>
  208. <Section title="1. TSC">
  209. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  210. <TextField size="small" label="Printer IP" value={tscConfig.ip} onChange={e => setTscConfig({...tscConfig, ip: e.target.value})} />
  211. <TextField size="small" label="Port" value={tscConfig.port} onChange={e => setTscConfig({...tscConfig, port: e.target.value})} />
  212. <SettingsEthernet color="action" />
  213. </Stack>
  214. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  215. <Table size="small" stickyHeader>
  216. <TableHead>
  217. <TableRow>
  218. <TableCell>Code</TableCell>
  219. <TableCell>Name</TableCell>
  220. <TableCell>Lot</TableCell>
  221. <TableCell>Expiry</TableCell>
  222. <TableCell align="center">Action</TableCell>
  223. </TableRow>
  224. </TableHead>
  225. <TableBody>
  226. {tscItems.map(row => (
  227. <TableRow key={row.id}>
  228. <TableCell><TextField variant="standard" value={row.itemCode} onChange={e => handleItemChange(setTscItems, row.id, 'itemCode', e.target.value)} /></TableCell>
  229. <TableCell><TextField variant="standard" value={row.itemName} onChange={e => handleItemChange(setTscItems, row.id, 'itemName', e.target.value)} /></TableCell>
  230. <TableCell><TextField variant="standard" value={row.lotNo} onChange={e => handleItemChange(setTscItems, row.id, 'lotNo', e.target.value)} /></TableCell>
  231. <TableCell><TextField variant="standard" type="date" value={row.expiryDate} onChange={e => handleItemChange(setTscItems, row.id, 'expiryDate', e.target.value)} /></TableCell>
  232. <TableCell align="center"><Button variant="contained" size="small" startIcon={<Print />} onClick={() => handleTscPrint(row)}>Print</Button></TableCell>
  233. </TableRow>
  234. ))}
  235. </TableBody>
  236. </Table>
  237. </TableContainer>
  238. </Section>
  239. </TabPanel>
  240. <TabPanel value={tabValue} index={1}>
  241. <Section title="2. DataFlex">
  242. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  243. <TextField size="small" label="Printer IP" value={dfConfig.ip} onChange={e => setDfConfig({...dfConfig, ip: e.target.value})} />
  244. <TextField size="small" label="Port" value={dfConfig.port} onChange={e => setDfConfig({...dfConfig, port: e.target.value})} />
  245. <Lan color="action" />
  246. </Stack>
  247. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  248. <Table size="small" stickyHeader>
  249. <TableHead>
  250. <TableRow>
  251. <TableCell>Code</TableCell>
  252. <TableCell>Name</TableCell>
  253. <TableCell>Lot</TableCell>
  254. <TableCell>Expiry</TableCell>
  255. <TableCell align="center">Action</TableCell>
  256. </TableRow>
  257. </TableHead>
  258. <TableBody>
  259. {dfItems.map(row => (
  260. <TableRow key={row.id}>
  261. <TableCell><TextField variant="standard" value={row.itemCode} onChange={e => handleItemChange(setDfItems, row.id, 'itemCode', e.target.value)} /></TableCell>
  262. <TableCell><TextField variant="standard" value={row.itemName} onChange={e => handleItemChange(setDfItems, row.id, 'itemName', e.target.value)} /></TableCell>
  263. <TableCell><TextField variant="standard" value={row.lotNo} onChange={e => handleItemChange(setDfItems, row.id, 'lotNo', e.target.value)} /></TableCell>
  264. <TableCell><TextField variant="standard" type="date" value={row.expiryDate} onChange={e => handleItemChange(setDfItems, row.id, 'expiryDate', e.target.value)} /></TableCell>
  265. <TableCell align="center"><Button variant="contained" color="secondary" size="small" startIcon={<Print />} onClick={() => handleDfPrint(row)}>Print</Button></TableCell>
  266. </TableRow>
  267. ))}
  268. </TableBody>
  269. </Table>
  270. </TableContainer>
  271. </Section>
  272. </TabPanel>
  273. <TabPanel value={tabValue} index={2}>
  274. <Section title="3. OnPack">
  275. <Box sx={{ m: 'auto', textAlign: 'center' }}>
  276. <Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
  277. Calls /plastic/get-printer6 to generate CoLOS .job bundle.
  278. </Typography>
  279. <Button variant="contained" color="success" size="large" startIcon={<FileDownload />} onClick={() => setIsPrinterModalOpen(true)}>
  280. Generate CoLOS Files
  281. </Button>
  282. </Box>
  283. </Section>
  284. </TabPanel>
  285. <TabPanel value={tabValue} index={3}>
  286. <Section title="4. Laser">
  287. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  288. <TextField size="small" label="Laser IP" value={laserConfig.ip} onChange={e => setLaserConfig({...laserConfig, ip: e.target.value})} />
  289. <TextField size="small" label="Port" value={laserConfig.port} onChange={e => setLaserConfig({...laserConfig, port: e.target.value})} />
  290. <Router color="action" />
  291. </Stack>
  292. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  293. <Table size="small" stickyHeader>
  294. <TableHead>
  295. <TableRow>
  296. <TableCell>Template</TableCell>
  297. <TableCell>Lot</TableCell>
  298. <TableCell>Exp</TableCell>
  299. <TableCell>Pwr%</TableCell>
  300. <TableCell align="center">Action</TableCell>
  301. </TableRow>
  302. </TableHead>
  303. <TableBody>
  304. {laserItems.map(row => (
  305. <TableRow key={row.id}>
  306. <TableCell><TextField variant="standard" value={row.templateId} onChange={e => handleItemChange(setLaserItems, row.id, 'templateId', e.target.value)} /></TableCell>
  307. <TableCell><TextField variant="standard" value={row.lotNo} onChange={e => handleItemChange(setLaserItems, row.id, 'lotNo', e.target.value)} /></TableCell>
  308. <TableCell><TextField variant="standard" type="date" value={row.expiryDate} onChange={e => handleItemChange(setLaserItems, row.id, 'expiryDate', e.target.value)} /></TableCell>
  309. <TableCell><TextField variant="standard" value={row.power} sx={{ width: 40 }} onChange={e => handleItemChange(setLaserItems, row.id, 'power', e.target.value)} /></TableCell>
  310. <TableCell align="center">
  311. <Stack direction="row" spacing={1} justifyContent="center">
  312. <Button
  313. variant="outlined"
  314. color="info"
  315. size="small"
  316. onClick={() => handleLaserPreview(row)}
  317. >
  318. Preview
  319. </Button>
  320. <Button
  321. variant="contained"
  322. color="warning"
  323. size="small"
  324. startIcon={<Print />}
  325. onClick={() => handleLaserPrint(row)}
  326. >
  327. Mark
  328. </Button>
  329. </Stack>
  330. </TableCell>
  331. </TableRow>
  332. ))}
  333. </TableBody>
  334. </Table>
  335. </TableContainer>
  336. <Typography variant="caption" sx={{ mt: 2, display: 'block', color: 'text.secondary' }}>
  337. Note: HANS Laser requires pre-saved templates on the controller.
  338. </Typography>
  339. </Section>
  340. </TabPanel>
  341. <TabPanel value={tabValue} index={4}>
  342. <Section title="5. HANS600S-M">
  343. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  344. <TextField
  345. size="small"
  346. label="Laser IP"
  347. value={hansConfig.ip}
  348. onChange={e => setHansConfig({...hansConfig, ip: e.target.value})}
  349. />
  350. <TextField
  351. size="small"
  352. label="Port"
  353. value={hansConfig.port}
  354. onChange={e => setHansConfig({...hansConfig, port: e.target.value})}
  355. />
  356. <Router color="action" sx={{ ml: 'auto' }} />
  357. </Stack>
  358. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  359. <Table size="small" stickyHeader>
  360. <TableHead>
  361. <TableRow>
  362. <TableCell>Ch3 Text (SN)</TableCell>
  363. <TableCell>Ch4 Text (Batch)</TableCell>
  364. <TableCell>Obj3 Name</TableCell>
  365. <TableCell>Obj4 Name</TableCell>
  366. <TableCell align="center">Action</TableCell>
  367. </TableRow>
  368. </TableHead>
  369. <TableBody>
  370. {hansItems.map(row => (
  371. <TableRow key={row.id}>
  372. <TableCell>
  373. <TextField
  374. variant="standard"
  375. value={row.textChannel3}
  376. onChange={e => handleItemChange(setHansItems, row.id, 'textChannel3', e.target.value)}
  377. sx={{ minWidth: 180 }}
  378. />
  379. </TableCell>
  380. <TableCell>
  381. <TextField
  382. variant="standard"
  383. value={row.textChannel4}
  384. onChange={e => handleItemChange(setHansItems, row.id, 'textChannel4', e.target.value)}
  385. sx={{ minWidth: 140 }}
  386. />
  387. </TableCell>
  388. <TableCell>
  389. <TextField
  390. variant="standard"
  391. value={row.text3ObjectName}
  392. onChange={e => handleItemChange(setHansItems, row.id, 'text3ObjectName', e.target.value)}
  393. size="small"
  394. />
  395. </TableCell>
  396. <TableCell>
  397. <TextField
  398. variant="standard"
  399. value={row.text4ObjectName}
  400. onChange={e => handleItemChange(setHansItems, row.id, 'text4ObjectName', e.target.value)}
  401. size="small"
  402. />
  403. </TableCell>
  404. <TableCell align="center">
  405. <Button
  406. variant="contained"
  407. color="error"
  408. size="small"
  409. startIcon={<Print />}
  410. onClick={() => handleHansPrint(row)}
  411. sx={{ minWidth: 80 }}
  412. >
  413. TCP Mark
  414. </Button>
  415. </TableCell>
  416. </TableRow>
  417. ))}
  418. </TableBody>
  419. </Table>
  420. </TableContainer>
  421. <Typography variant="caption" sx={{ mt: 2, display: 'block', color: 'text.secondary', fontSize: '0.75rem' }}>
  422. TCP Push to EZCAD3 (Ch3/Ch4 via E3_SetTextObject) | IP:192.168.76.10:45678 | Backend: /print-laser-tcp
  423. </Typography>
  424. </Section>
  425. </TabPanel>
  426. {/* Dialog for OnPack */}
  427. <Dialog open={isPrinterModalOpen} onClose={() => setIsPrinterModalOpen(false)} fullWidth maxWidth="sm">
  428. <DialogTitle sx={{ bgcolor: 'success.main', color: 'white' }}>OnPack Printer Job Details</DialogTitle>
  429. <DialogContent sx={{ mt: 2 }}>
  430. <Stack spacing={3}>
  431. <TextField label="Item Code" fullWidth value={printerFormData.itemCode} onChange={(e) => setPrinterFormData({ ...printerFormData, itemCode: e.target.value })} />
  432. <TextField label="Lot Number" fullWidth value={printerFormData.lotNo} onChange={(e) => setPrinterFormData({ ...printerFormData, lotNo: e.target.value })} />
  433. <TextField label="Product Name" fullWidth value={printerFormData.productName} onChange={(e) => setPrinterFormData({ ...printerFormData, productName: e.target.value })} />
  434. <TextField label="Expiry Date" type="date" fullWidth InputLabelProps={{ shrink: true }} value={printerFormData.expiryDate} onChange={(e) => setPrinterFormData({ ...printerFormData, expiryDate: e.target.value })} />
  435. </Stack>
  436. </DialogContent>
  437. <DialogActions sx={{ p: 3 }}>
  438. <Button onClick={() => setIsPrinterModalOpen(false)} variant="outlined" color="inherit">Cancel</Button>
  439. <Button variant="contained" color="success" onClick={handleDownloadPrintJob}>Generate & Download</Button>
  440. </DialogActions>
  441. </Dialog>
  442. </Box>
  443. );
  444. }