FPSMS-frontend
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 

613 linhas
26 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. import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
  13. import * as XLSX from "xlsx";
  14. // Simple TabPanel component for conditional rendering
  15. interface TabPanelProps {
  16. children?: React.ReactNode;
  17. index: number;
  18. value: number;
  19. }
  20. function TabPanel(props: TabPanelProps) {
  21. const { children, value, index, ...other } = props;
  22. return (
  23. <div
  24. role="tabpanel"
  25. hidden={value !== index}
  26. id={`simple-tabpanel-${index}`}
  27. aria-labelledby={`simple-tab-${index}`}
  28. {...other}
  29. >
  30. {value === index && (
  31. <Box sx={{ p: 3 }}>
  32. {children}
  33. </Box>
  34. )}
  35. </div>
  36. );
  37. }
  38. export default function TestingPage() {
  39. // Tab state
  40. const [tabValue, setTabValue] = useState(0);
  41. const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
  42. setTabValue(newValue);
  43. };
  44. // --- 1. TSC Section States ---
  45. const [tscConfig, setTscConfig] = useState({ ip: '192.168.1.100', port: '9100' });
  46. const [tscItems, setTscItems] = useState([
  47. { id: 1, itemCode: 'FG-001', itemName: 'Yellow Curry Sauce', lotNo: 'LOT-TSC-01', expiryDate: '2025-12-01' },
  48. { id: 2, itemCode: 'FG-002', itemName: 'Red Curry Paste', lotNo: 'LOT-TSC-02', expiryDate: '2025-12-05' },
  49. ]);
  50. // --- 2. DataFlex Section States ---
  51. const [dfConfig, setDfConfig] = useState({ ip: '192.168.1.101', port: '9100' });
  52. const [dfItems, setDfItems] = useState([
  53. { id: 1, itemCode: 'DF-101', itemName: 'Instant Noodle A', lotNo: 'LOT-DF-01', expiryDate: '2026-01-10' },
  54. { id: 2, itemCode: 'DF-102', itemName: 'Instant Noodle B', lotNo: 'LOT-DF-02', expiryDate: '2026-01-15' },
  55. ]);
  56. // --- 3. OnPack Section States ---
  57. const [isPrinterModalOpen, setIsPrinterModalOpen] = useState(false);
  58. const [printerFormData, setPrinterFormData] = useState({
  59. itemCode: '',
  60. lotNo: '',
  61. expiryDate: dayjs().format('YYYY-MM-DD'),
  62. productName: ''
  63. });
  64. // --- 4. Laser Section States ---
  65. const [laserConfig, setLaserConfig] = useState({ ip: '192.168.1.102', port: '8080' });
  66. const [laserItems, setLaserItems] = useState([
  67. { id: 1, templateId: 'JOB_001', lotNo: 'L-LASER-01', expiryDate: '2025-12-31', power: '50' },
  68. ]);
  69. // --- 5. HANS600S-M Section States ---
  70. const [hansConfig, setHansConfig] = useState({ ip: '192.168.76.10', port: '45678' });
  71. const [hansItems, setHansItems] = useState([
  72. {
  73. id: 1,
  74. textChannel3: 'SN-HANS-001-20260117', // channel 3 (e.g. serial / text1)
  75. textChannel4: 'BATCH-HK-TEST-OK', // channel 4 (e.g. batch / text2)
  76. text3ObjectName: 'Text3', // EZCAD object name for channel 3
  77. text4ObjectName: 'Text4' // EZCAD object name for channel 4
  78. },
  79. ]);
  80. // --- 6. GRN Preview (M18) ---
  81. const [grnPreviewReceiptDate, setGrnPreviewReceiptDate] = useState("2026-03-16");
  82. // --- 7. M18 PO Sync by Code ---
  83. const [m18PoCode, setM18PoCode] = useState("");
  84. const [isSyncingM18Po, setIsSyncingM18Po] = useState(false);
  85. const [m18PoSyncResult, setM18PoSyncResult] = useState<string>("");
  86. // Generic handler for inline table edits
  87. const handleItemChange = (setter: any, id: number, field: string, value: string) => {
  88. setter((prev: any[]) => prev.map(item =>
  89. item.id === id ? { ...item, [field]: value } : item
  90. ));
  91. };
  92. // --- API CALLS ---
  93. // TSC Print (Section 1)
  94. const handleTscPrint = async (row: any) => {
  95. const payload = { ...row, printerIp: tscConfig.ip, printerPort: tscConfig.port };
  96. try {
  97. const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-tsc`, {
  98. method: 'POST',
  99. headers: { 'Content-Type': 'application/json' },
  100. body: JSON.stringify(payload)
  101. });
  102. if (response.status === 401 || response.status === 403) return;
  103. if (response.ok) alert(`TSC Print Command Sent for ${row.itemCode}!`);
  104. else alert("TSC Print Failed");
  105. } catch (e) { console.error("TSC Error:", e); }
  106. };
  107. // DataFlex Print (Section 2)
  108. const handleDfPrint = async (row: any) => {
  109. const payload = { ...row, printerIp: dfConfig.ip, printerPort: dfConfig.port };
  110. try {
  111. const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-dataflex`, {
  112. method: 'POST',
  113. headers: { 'Content-Type': 'application/json' },
  114. body: JSON.stringify(payload)
  115. });
  116. if (response.status === 401 || response.status === 403) return;
  117. if (response.ok) alert(`DataFlex Print Command Sent for ${row.itemCode}!`);
  118. else alert("DataFlex Print Failed");
  119. } catch (e) { console.error("DataFlex Error:", e); }
  120. };
  121. // OnPack Zip Download (Section 3)
  122. const handleDownloadPrintJob = async () => {
  123. const params = new URLSearchParams(printerFormData);
  124. try {
  125. const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/get-printer6?${params.toString()}`, {
  126. method: 'GET',
  127. });
  128. if (response.status === 401 || response.status === 403) return;
  129. if (!response.ok) throw new Error('Download failed');
  130. const blob = await response.blob();
  131. const url = window.URL.createObjectURL(blob);
  132. const link = document.createElement('a');
  133. link.href = url;
  134. link.setAttribute('download', `${printerFormData.lotNo || 'OnPack'}.zip`);
  135. document.body.appendChild(link);
  136. link.click();
  137. link.remove();
  138. window.URL.revokeObjectURL(url);
  139. setIsPrinterModalOpen(false);
  140. } catch (e) { console.error("OnPack Error:", e); }
  141. };
  142. // Laser Print (Section 4 - original)
  143. const handleLaserPrint = async (row: any) => {
  144. const payload = { ...row, printerIp: laserConfig.ip, printerPort: laserConfig.port };
  145. try {
  146. const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser`, {
  147. method: 'POST',
  148. headers: { 'Content-Type': 'application/json' },
  149. body: JSON.stringify(payload)
  150. });
  151. if (response.status === 401 || response.status === 403) return;
  152. if (response.ok) alert(`Laser Command Sent: ${row.templateId}`);
  153. } catch (e) { console.error(e); }
  154. };
  155. const handleLaserPreview = async (row: any) => {
  156. const payload = { ...row, printerIp: laserConfig.ip, printerPort: parseInt(laserConfig.port) };
  157. try {
  158. const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/preview-laser`, {
  159. method: 'POST',
  160. headers: { 'Content-Type': 'application/json' },
  161. body: JSON.stringify(payload)
  162. });
  163. if (response.status === 401 || response.status === 403) return;
  164. if (response.ok) alert("Red light preview active!");
  165. } catch (e) { console.error("Preview Error:", e); }
  166. };
  167. // HANS600S-M TCP Print (Section 5)
  168. const handleHansPrint = async (row: any) => {
  169. const payload = {
  170. printerIp: hansConfig.ip,
  171. printerPort: hansConfig.port,
  172. textChannel3: row.textChannel3,
  173. textChannel4: row.textChannel4,
  174. text3ObjectName: row.text3ObjectName,
  175. text4ObjectName: row.text4ObjectName
  176. };
  177. try {
  178. const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser-tcp`, {
  179. method: 'POST',
  180. headers: { 'Content-Type': 'application/json' },
  181. body: JSON.stringify(payload)
  182. });
  183. if (response.status === 401 || response.status === 403) return;
  184. const result = await response.text();
  185. if (response.ok) {
  186. alert(`HANS600S-M Mark Success: ${result}`);
  187. } else {
  188. alert(`HANS600S-M Failed: ${result}`);
  189. }
  190. } catch (e) {
  191. console.error("HANS600S-M Error:", e);
  192. alert("HANS600S-M Connection Error");
  193. }
  194. };
  195. // GRN Preview CSV Download (Section 6)
  196. const handleDownloadGrnPreviewXlsx = async () => {
  197. try {
  198. const response = await clientAuthFetch(
  199. `${NEXT_PUBLIC_API_URL}/report/grn-preview-m18?receiptDate=${encodeURIComponent(grnPreviewReceiptDate)}`,
  200. { method: "GET" },
  201. );
  202. if (response.status === 401 || response.status === 403) return;
  203. if (!response.ok) throw new Error(`Download failed: ${response.status}`);
  204. const data = await response.json();
  205. const rows = Array.isArray(data?.rows) ? data.rows : [];
  206. const ws = XLSX.utils.json_to_sheet(rows);
  207. const wb = XLSX.utils.book_new();
  208. XLSX.utils.book_append_sheet(wb, ws, "GRN Preview");
  209. const xlsxArrayBuffer = XLSX.write(wb, { bookType: "xlsx", type: "array" });
  210. const blob = new Blob([xlsxArrayBuffer], {
  211. type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  212. });
  213. const url = window.URL.createObjectURL(blob);
  214. const link = document.createElement("a");
  215. link.href = url;
  216. link.setAttribute("download", `grn-preview-m18-${grnPreviewReceiptDate}.xlsx`);
  217. document.body.appendChild(link);
  218. link.click();
  219. link.remove();
  220. window.URL.revokeObjectURL(url);
  221. } catch (e) {
  222. console.error("GRN Preview XLSX Download Error:", e);
  223. alert("GRN Preview XLSX download failed. Check console/network.");
  224. }
  225. };
  226. // M18 PO Sync By Code (Section 7)
  227. const handleSyncM18PoByCode = async () => {
  228. if (!m18PoCode.trim()) {
  229. alert("Please enter PO code.");
  230. return;
  231. }
  232. setIsSyncingM18Po(true);
  233. setM18PoSyncResult("");
  234. try {
  235. const response = await clientAuthFetch(
  236. `${NEXT_PUBLIC_API_URL}/m18/test/po-by-code?code=${encodeURIComponent(m18PoCode.trim())}`,
  237. { method: "GET" },
  238. );
  239. if (response.status === 401 || response.status === 403) return;
  240. const text = await response.text();
  241. setM18PoSyncResult(text);
  242. if (!response.ok) {
  243. alert(`Sync failed: ${response.status}`);
  244. }
  245. } catch (e) {
  246. console.error("M18 PO Sync By Code Error:", e);
  247. alert("M18 PO sync failed. Check console/network.");
  248. } finally {
  249. setIsSyncingM18Po(false);
  250. }
  251. };
  252. // Layout Helper
  253. const Section = ({ title, children }: { title: string, children?: React.ReactNode }) => (
  254. <Paper sx={{ p: 3, minHeight: '450px', display: 'flex', flexDirection: 'column' }}>
  255. <Typography variant="h5" gutterBottom color="primary" sx={{ borderBottom: '2px solid #f0f0f0', pb: 1, mb: 2 }}>
  256. {title}
  257. </Typography>
  258. {children || <Typography color="textSecondary" sx={{ m: 'auto' }}>Waiting for implementation...</Typography>}
  259. </Paper>
  260. );
  261. return (
  262. <Box sx={{ p: 4 }}>
  263. <Typography variant="h4" sx={{ mb: 4, fontWeight: 'bold' }}>Printer Testing</Typography>
  264. <Tabs value={tabValue} onChange={handleTabChange} aria-label="printer sections tabs" centered variant="fullWidth">
  265. <Tab label="1. TSC" />
  266. <Tab label="2. DataFlex" />
  267. <Tab label="3. OnPack" />
  268. <Tab label="4. Laser" />
  269. <Tab label="5. HANS600S-M" />
  270. <Tab label="6. GRN Preview" />
  271. <Tab label="7. M18 PO Sync" />
  272. </Tabs>
  273. <TabPanel value={tabValue} index={0}>
  274. <Section title="1. TSC">
  275. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  276. <TextField size="small" label="Printer IP" value={tscConfig.ip} onChange={e => setTscConfig({...tscConfig, ip: e.target.value})} />
  277. <TextField size="small" label="Port" value={tscConfig.port} onChange={e => setTscConfig({...tscConfig, port: e.target.value})} />
  278. <SettingsEthernet color="action" />
  279. </Stack>
  280. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  281. <Table size="small" stickyHeader>
  282. <TableHead>
  283. <TableRow>
  284. <TableCell>Code</TableCell>
  285. <TableCell>Name</TableCell>
  286. <TableCell>Lot</TableCell>
  287. <TableCell>Expiry</TableCell>
  288. <TableCell align="center">Action</TableCell>
  289. </TableRow>
  290. </TableHead>
  291. <TableBody>
  292. {tscItems.map(row => (
  293. <TableRow key={row.id}>
  294. <TableCell><TextField variant="standard" value={row.itemCode} onChange={e => handleItemChange(setTscItems, row.id, 'itemCode', e.target.value)} /></TableCell>
  295. <TableCell><TextField variant="standard" value={row.itemName} onChange={e => handleItemChange(setTscItems, row.id, 'itemName', e.target.value)} /></TableCell>
  296. <TableCell><TextField variant="standard" value={row.lotNo} onChange={e => handleItemChange(setTscItems, row.id, 'lotNo', e.target.value)} /></TableCell>
  297. <TableCell><TextField variant="standard" type="date" value={row.expiryDate} onChange={e => handleItemChange(setTscItems, row.id, 'expiryDate', e.target.value)} /></TableCell>
  298. <TableCell align="center"><Button variant="contained" size="small" startIcon={<Print />} onClick={() => handleTscPrint(row)}>Print</Button></TableCell>
  299. </TableRow>
  300. ))}
  301. </TableBody>
  302. </Table>
  303. </TableContainer>
  304. </Section>
  305. </TabPanel>
  306. <TabPanel value={tabValue} index={1}>
  307. <Section title="2. DataFlex">
  308. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  309. <TextField size="small" label="Printer IP" value={dfConfig.ip} onChange={e => setDfConfig({...dfConfig, ip: e.target.value})} />
  310. <TextField size="small" label="Port" value={dfConfig.port} onChange={e => setDfConfig({...dfConfig, port: e.target.value})} />
  311. <Lan color="action" />
  312. </Stack>
  313. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  314. <Table size="small" stickyHeader>
  315. <TableHead>
  316. <TableRow>
  317. <TableCell>Code</TableCell>
  318. <TableCell>Name</TableCell>
  319. <TableCell>Lot</TableCell>
  320. <TableCell>Expiry</TableCell>
  321. <TableCell align="center">Action</TableCell>
  322. </TableRow>
  323. </TableHead>
  324. <TableBody>
  325. {dfItems.map(row => (
  326. <TableRow key={row.id}>
  327. <TableCell><TextField variant="standard" value={row.itemCode} onChange={e => handleItemChange(setDfItems, row.id, 'itemCode', e.target.value)} /></TableCell>
  328. <TableCell><TextField variant="standard" value={row.itemName} onChange={e => handleItemChange(setDfItems, row.id, 'itemName', e.target.value)} /></TableCell>
  329. <TableCell><TextField variant="standard" value={row.lotNo} onChange={e => handleItemChange(setDfItems, row.id, 'lotNo', e.target.value)} /></TableCell>
  330. <TableCell><TextField variant="standard" type="date" value={row.expiryDate} onChange={e => handleItemChange(setDfItems, row.id, 'expiryDate', e.target.value)} /></TableCell>
  331. <TableCell align="center"><Button variant="contained" color="secondary" size="small" startIcon={<Print />} onClick={() => handleDfPrint(row)}>Print</Button></TableCell>
  332. </TableRow>
  333. ))}
  334. </TableBody>
  335. </Table>
  336. </TableContainer>
  337. </Section>
  338. </TabPanel>
  339. <TabPanel value={tabValue} index={2}>
  340. <Section title="3. OnPack">
  341. <Box sx={{ m: 'auto', textAlign: 'center' }}>
  342. <Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
  343. Calls /plastic/get-printer6 to generate CoLOS .job bundle.
  344. </Typography>
  345. <Button variant="contained" color="success" size="large" startIcon={<FileDownload />} onClick={() => setIsPrinterModalOpen(true)}>
  346. Generate CoLOS Files
  347. </Button>
  348. </Box>
  349. </Section>
  350. </TabPanel>
  351. <TabPanel value={tabValue} index={3}>
  352. <Section title="4. Laser">
  353. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  354. <TextField size="small" label="Laser IP" value={laserConfig.ip} onChange={e => setLaserConfig({...laserConfig, ip: e.target.value})} />
  355. <TextField size="small" label="Port" value={laserConfig.port} onChange={e => setLaserConfig({...laserConfig, port: e.target.value})} />
  356. <Router color="action" />
  357. </Stack>
  358. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  359. <Table size="small" stickyHeader>
  360. <TableHead>
  361. <TableRow>
  362. <TableCell>Template</TableCell>
  363. <TableCell>Lot</TableCell>
  364. <TableCell>Exp</TableCell>
  365. <TableCell>Pwr%</TableCell>
  366. <TableCell align="center">Action</TableCell>
  367. </TableRow>
  368. </TableHead>
  369. <TableBody>
  370. {laserItems.map(row => (
  371. <TableRow key={row.id}>
  372. <TableCell><TextField variant="standard" value={row.templateId} onChange={e => handleItemChange(setLaserItems, row.id, 'templateId', e.target.value)} /></TableCell>
  373. <TableCell><TextField variant="standard" value={row.lotNo} onChange={e => handleItemChange(setLaserItems, row.id, 'lotNo', e.target.value)} /></TableCell>
  374. <TableCell><TextField variant="standard" type="date" value={row.expiryDate} onChange={e => handleItemChange(setLaserItems, row.id, 'expiryDate', e.target.value)} /></TableCell>
  375. <TableCell><TextField variant="standard" value={row.power} sx={{ width: 40 }} onChange={e => handleItemChange(setLaserItems, row.id, 'power', e.target.value)} /></TableCell>
  376. <TableCell align="center">
  377. <Stack direction="row" spacing={1} justifyContent="center">
  378. <Button
  379. variant="outlined"
  380. color="info"
  381. size="small"
  382. onClick={() => handleLaserPreview(row)}
  383. >
  384. Preview
  385. </Button>
  386. <Button
  387. variant="contained"
  388. color="warning"
  389. size="small"
  390. startIcon={<Print />}
  391. onClick={() => handleLaserPrint(row)}
  392. >
  393. Mark
  394. </Button>
  395. </Stack>
  396. </TableCell>
  397. </TableRow>
  398. ))}
  399. </TableBody>
  400. </Table>
  401. </TableContainer>
  402. <Typography variant="caption" sx={{ mt: 2, display: 'block', color: 'text.secondary' }}>
  403. Note: HANS Laser requires pre-saved templates on the controller.
  404. </Typography>
  405. </Section>
  406. </TabPanel>
  407. <TabPanel value={tabValue} index={4}>
  408. <Section title="5. HANS600S-M">
  409. <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
  410. <TextField
  411. size="small"
  412. label="Laser IP"
  413. value={hansConfig.ip}
  414. onChange={e => setHansConfig({...hansConfig, ip: e.target.value})}
  415. />
  416. <TextField
  417. size="small"
  418. label="Port"
  419. value={hansConfig.port}
  420. onChange={e => setHansConfig({...hansConfig, port: e.target.value})}
  421. />
  422. <Router color="action" sx={{ ml: 'auto' }} />
  423. </Stack>
  424. <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
  425. <Table size="small" stickyHeader>
  426. <TableHead>
  427. <TableRow>
  428. <TableCell>Ch3 Text (SN)</TableCell>
  429. <TableCell>Ch4 Text (Batch)</TableCell>
  430. <TableCell>Obj3 Name</TableCell>
  431. <TableCell>Obj4 Name</TableCell>
  432. <TableCell align="center">Action</TableCell>
  433. </TableRow>
  434. </TableHead>
  435. <TableBody>
  436. {hansItems.map(row => (
  437. <TableRow key={row.id}>
  438. <TableCell>
  439. <TextField
  440. variant="standard"
  441. value={row.textChannel3}
  442. onChange={e => handleItemChange(setHansItems, row.id, 'textChannel3', e.target.value)}
  443. sx={{ minWidth: 180 }}
  444. />
  445. </TableCell>
  446. <TableCell>
  447. <TextField
  448. variant="standard"
  449. value={row.textChannel4}
  450. onChange={e => handleItemChange(setHansItems, row.id, 'textChannel4', e.target.value)}
  451. sx={{ minWidth: 140 }}
  452. />
  453. </TableCell>
  454. <TableCell>
  455. <TextField
  456. variant="standard"
  457. value={row.text3ObjectName}
  458. onChange={e => handleItemChange(setHansItems, row.id, 'text3ObjectName', e.target.value)}
  459. size="small"
  460. />
  461. </TableCell>
  462. <TableCell>
  463. <TextField
  464. variant="standard"
  465. value={row.text4ObjectName}
  466. onChange={e => handleItemChange(setHansItems, row.id, 'text4ObjectName', e.target.value)}
  467. size="small"
  468. />
  469. </TableCell>
  470. <TableCell align="center">
  471. <Button
  472. variant="contained"
  473. color="error"
  474. size="small"
  475. startIcon={<Print />}
  476. onClick={() => handleHansPrint(row)}
  477. sx={{ minWidth: 80 }}
  478. >
  479. TCP Mark
  480. </Button>
  481. </TableCell>
  482. </TableRow>
  483. ))}
  484. </TableBody>
  485. </Table>
  486. </TableContainer>
  487. <Typography variant="caption" sx={{ mt: 2, display: 'block', color: 'text.secondary', fontSize: '0.75rem' }}>
  488. TCP Push to EZCAD3 (Ch3/Ch4 via E3_SetTextObject) | IP:192.168.76.10:45678 | Backend: /print-laser-tcp
  489. </Typography>
  490. </Section>
  491. </TabPanel>
  492. <TabPanel value={tabValue} index={5}>
  493. <Section title="6. GRN Preview (M18)">
  494. <Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: "center" }}>
  495. <TextField
  496. size="small"
  497. label="Receipt Date"
  498. type="date"
  499. value={grnPreviewReceiptDate}
  500. onChange={(e) => setGrnPreviewReceiptDate(e.target.value)}
  501. InputLabelProps={{ shrink: true }}
  502. />
  503. <Button
  504. variant="contained"
  505. color="success"
  506. size="medium"
  507. startIcon={<FileDownload />}
  508. onClick={handleDownloadGrnPreviewXlsx}
  509. >
  510. Download GRN Preview XLSX
  511. </Button>
  512. </Stack>
  513. <Typography variant="body2" color="textSecondary">
  514. Backend endpoint: <code>/report/grn-preview-m18?receiptDate=YYYY-MM-DD</code>
  515. </Typography>
  516. </Section>
  517. </TabPanel>
  518. <TabPanel value={tabValue} index={6}>
  519. <Section title="7. M18 PO Sync by Code">
  520. <Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: "center" }}>
  521. <TextField
  522. size="small"
  523. label="PO Code"
  524. value={m18PoCode}
  525. onChange={(e) => setM18PoCode(e.target.value)}
  526. placeholder="e.g. PFP002PO26030341"
  527. sx={{ minWidth: 320 }}
  528. />
  529. <Button
  530. variant="contained"
  531. color="primary"
  532. onClick={handleSyncM18PoByCode}
  533. disabled={isSyncingM18Po}
  534. >
  535. {isSyncingM18Po ? "Syncing..." : "Sync PO from M18"}
  536. </Button>
  537. </Stack>
  538. <Typography variant="body2" color="textSecondary">
  539. Backend endpoint: <code>/m18/test/po-by-code?code=YOUR_CODE</code>
  540. </Typography>
  541. {m18PoSyncResult ? (
  542. <TextField
  543. fullWidth
  544. multiline
  545. minRows={4}
  546. margin="normal"
  547. label="Sync Result"
  548. value={m18PoSyncResult}
  549. InputProps={{ readOnly: true }}
  550. />
  551. ) : null}
  552. </Section>
  553. </TabPanel>
  554. {/* Dialog for OnPack */}
  555. <Dialog open={isPrinterModalOpen} onClose={() => setIsPrinterModalOpen(false)} fullWidth maxWidth="sm">
  556. <DialogTitle sx={{ bgcolor: 'success.main', color: 'white' }}>OnPack Printer Job Details</DialogTitle>
  557. <DialogContent sx={{ mt: 2 }}>
  558. <Stack spacing={3}>
  559. <TextField label="Item Code" fullWidth value={printerFormData.itemCode} onChange={(e) => setPrinterFormData({ ...printerFormData, itemCode: e.target.value })} />
  560. <TextField label="Lot Number" fullWidth value={printerFormData.lotNo} onChange={(e) => setPrinterFormData({ ...printerFormData, lotNo: e.target.value })} />
  561. <TextField label="Product Name" fullWidth value={printerFormData.productName} onChange={(e) => setPrinterFormData({ ...printerFormData, productName: e.target.value })} />
  562. <TextField label="Expiry Date" type="date" fullWidth InputLabelProps={{ shrink: true }} value={printerFormData.expiryDate} onChange={(e) => setPrinterFormData({ ...printerFormData, expiryDate: e.target.value })} />
  563. </Stack>
  564. </DialogContent>
  565. <DialogActions sx={{ p: 3 }}>
  566. <Button onClick={() => setIsPrinterModalOpen(false)} variant="outlined" color="inherit">Cancel</Button>
  567. <Button variant="contained" color="success" onClick={handleDownloadPrintJob}>Generate & Download</Button>
  568. </DialogActions>
  569. </Dialog>
  570. </Box>
  571. );
  572. }