diff --git a/src/app/(main)/testing/page.tsx b/src/app/(main)/testing/page.tsx
new file mode 100644
index 0000000..4c64f92
--- /dev/null
+++ b/src/app/(main)/testing/page.tsx
@@ -0,0 +1,306 @@
+"use client";
+
+import React, { useState } from "react";
+import {
+ Box, Grid, Paper, Typography, Button, Dialog, DialogTitle,
+ DialogContent, DialogActions, TextField, Stack, Table,
+ TableBody, TableCell, TableContainer, TableHead, TableRow
+} from "@mui/material";
+import { FileDownload, Print, SettingsEthernet, Lan, Router } from "@mui/icons-material";
+import dayjs from "dayjs";
+import { NEXT_PUBLIC_API_URL } from "@/config/api";
+
+export default function TestingPage() {
+ // --- 1. TSC Section States ---
+ const [tscConfig, setTscConfig] = useState({ ip: '192.168.1.100', port: '9100' });
+ const [tscItems, setTscItems] = useState([
+ { id: 1, itemCode: 'FG-001', itemName: 'Yellow Curry Sauce', lotNo: 'LOT-TSC-01', expiryDate: '2025-12-01' },
+ { id: 2, itemCode: 'FG-002', itemName: 'Red Curry Paste', lotNo: 'LOT-TSC-02', expiryDate: '2025-12-05' },
+ ]);
+
+ // --- 2. DataFlex Section States ---
+ const [dfConfig, setDfConfig] = useState({ ip: '192.168.1.101', port: '9100' });
+ const [dfItems, setDfItems] = useState([
+ { id: 1, itemCode: 'DF-101', itemName: 'Instant Noodle A', lotNo: 'LOT-DF-01', expiryDate: '2026-01-10' },
+ { id: 2, itemCode: 'DF-102', itemName: 'Instant Noodle B', lotNo: 'LOT-DF-02', expiryDate: '2026-01-15' },
+ ]);
+
+ // --- 3. OnPack Section States ---
+ const [isPrinterModalOpen, setIsPrinterModalOpen] = useState(false);
+ const [printerFormData, setPrinterFormData] = useState({
+ itemCode: '',
+ lotNo: '',
+ expiryDate: dayjs().format('YYYY-MM-DD'),
+ productName: ''
+ });
+
+ // --- 4. Laser Section States ---
+const [laserConfig, setLaserConfig] = useState({ ip: '192.168.1.102', port: '8080' });
+const [laserItems, setLaserItems] = useState([
+ { id: 1, templateId: 'JOB_001', lotNo: 'L-LASER-01', expiryDate: '2025-12-31', power: '50' },
+]);
+
+ // Generic handler for inline table edits
+ const handleItemChange = (setter: any, id: number, field: string, value: string) => {
+ setter((prev: any[]) => prev.map(item =>
+ item.id === id ? { ...item, [field]: value } : item
+ ));
+ };
+
+ // --- API CALLS ---
+
+ // TSC Print (Section 1)
+ const handleTscPrint = async (row: any) => {
+ const token = localStorage.getItem("accessToken");
+ const payload = { ...row, printerIp: tscConfig.ip, printerPort: tscConfig.port };
+ try {
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-tsc`, {
+ method: 'POST',
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+ if (response.ok) alert(`TSC Print Command Sent for ${row.itemCode}!`);
+ else alert("TSC Print Failed");
+ } catch (e) { console.error("TSC Error:", e); }
+ };
+
+ // DataFlex Print (Section 2)
+ const handleDfPrint = async (row: any) => {
+ const token = localStorage.getItem("accessToken");
+ const payload = { ...row, printerIp: dfConfig.ip, printerPort: dfConfig.port };
+ try {
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-dataflex`, {
+ method: 'POST',
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+ if (response.ok) alert(`DataFlex Print Command Sent for ${row.itemCode}!`);
+ else alert("DataFlex Print Failed");
+ } catch (e) { console.error("DataFlex Error:", e); }
+ };
+
+ // OnPack Zip Download (Section 3)
+ const handleDownloadPrintJob = async () => {
+ const token = localStorage.getItem("accessToken");
+ const params = new URLSearchParams(printerFormData);
+ try {
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/get-printer6?${params.toString()}`, {
+ method: 'GET',
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+
+ if (!response.ok) throw new Error('Download failed');
+
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', `${printerFormData.lotNo || 'OnPack'}.zip`);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ window.URL.revokeObjectURL(url);
+
+ setIsPrinterModalOpen(false);
+ } catch (e) { console.error("OnPack Error:", e); }
+ };
+
+ const handleLaserPrint = async (row: any) => {
+ const token = localStorage.getItem("accessToken");
+ const payload = { ...row, printerIp: laserConfig.ip, printerPort: laserConfig.port };
+ try {
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser`, {
+ method: 'POST',
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+ if (response.ok) alert(`Laser Command Sent: ${row.templateId}`);
+ } catch (e) { console.error(e); }
+ };
+
+ const handleLaserPreview = async (row: any) => {
+ const token = localStorage.getItem("accessToken");
+ const payload = { ...row, printerIp: laserConfig.ip, printerPort: parseInt(laserConfig.port) };
+ try {
+ // We'll create this endpoint in the backend next
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/preview-laser`, {
+ method: 'POST',
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+ if (response.ok) alert("Red light preview active!");
+ } catch (e) { console.error("Preview Error:", e); }
+ };
+
+ // Layout Helper
+ const Section = ({ title, children }: { title: string, children?: React.ReactNode }) => (
+
+
+
+ {title}
+
+ {children || Waiting for implementation...}
+
+
+ );
+
+ return (
+
+ Printer Testing Dashboard
+
+
+ {/* 1. TSC Section */}
+
+
+ setTscConfig({...tscConfig, ip: e.target.value})} />
+ setTscConfig({...tscConfig, port: e.target.value})} />
+
+
+
+
+
+
+ Code
+ Name
+ Lot
+ Expiry
+ Action
+
+
+
+ {tscItems.map(row => (
+
+ handleItemChange(setTscItems, row.id, 'itemCode', e.target.value)} />
+ handleItemChange(setTscItems, row.id, 'itemName', e.target.value)} />
+ handleItemChange(setTscItems, row.id, 'lotNo', e.target.value)} />
+ handleItemChange(setTscItems, row.id, 'expiryDate', e.target.value)} />
+ } onClick={() => handleTscPrint(row)}>Print
+
+ ))}
+
+
+
+
+
+ {/* 2. DataFlex Section */}
+
+
+ setDfConfig({...dfConfig, ip: e.target.value})} />
+ setDfConfig({...dfConfig, port: e.target.value})} />
+
+
+
+
+
+
+ Code
+ Name
+ Lot
+ Expiry
+ Action
+
+
+
+ {dfItems.map(row => (
+
+ handleItemChange(setDfItems, row.id, 'itemCode', e.target.value)} />
+ handleItemChange(setDfItems, row.id, 'itemName', e.target.value)} />
+ handleItemChange(setDfItems, row.id, 'lotNo', e.target.value)} />
+ handleItemChange(setDfItems, row.id, 'expiryDate', e.target.value)} />
+ } onClick={() => handleDfPrint(row)}>Print
+
+ ))}
+
+
+
+
+
+ {/* 3. OnPack Section */}
+
+
+
+ Calls /plastic/get-printer6 to generate CoLOS .job bundle.
+
+ } onClick={() => setIsPrinterModalOpen(true)}>
+ Generate CoLOS Files
+
+
+
+
+ {/* 4. Laser Section (HANS600S-M) */}
+
+
+ setLaserConfig({...laserConfig, ip: e.target.value})} />
+ setLaserConfig({...laserConfig, port: e.target.value})} />
+
+
+
+
+
+
+
+ Template
+ Lot
+ Exp
+ Pwr%
+ Action
+
+
+
+ {laserItems.map(row => (
+
+ handleItemChange(setLaserItems, row.id, 'templateId', e.target.value)} />
+ handleItemChange(setLaserItems, row.id, 'lotNo', e.target.value)} />
+ handleItemChange(setLaserItems, row.id, 'expiryDate', e.target.value)} />
+ handleItemChange(setLaserItems, row.id, 'power', e.target.value)} />
+
+
+
+ }
+ onClick={() => handleLaserPrint(row)}
+ >
+ Mark
+
+
+
+
+ ))}
+
+
+
+
+ Note: HANS Laser requires pre-saved templates on the controller.
+
+
+
+
+ {/* Dialog for OnPack */}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/api/scheduling/actions.ts b/src/app/api/scheduling/actions.ts
index 0ed01bb..ca5e48f 100644
--- a/src/app/api/scheduling/actions.ts
+++ b/src/app/api/scheduling/actions.ts
@@ -43,6 +43,13 @@ export interface ReleaseProdScheduleReq {
id: number;
}
+export interface print6FilesReq {
+ itemCode: 'string',
+ lotNo: 'string',
+ expiryDate: 'string',
+ productName: 'string'
+}
+
export interface ReleaseProdScheduleResponse {
id: number;
code: string;
@@ -145,6 +152,23 @@ export const testDetailedSchedule = cache(async (date?: string) => {
);
});
+export const getFile6 = cache(async (
+ token: string | "",
+ data: print6FilesReq
+) => {
+ const queryStr = convertObjToURLSearchParams(data)
+ return serverFetchJson(
+ `${BASE_API_URL}/plastic/get-printer6?${queryStr}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`
+ },
+ },
+ );
+});
+
export const releaseProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => {
const response = serverFetchJson(
`${BASE_API_URL}/productionSchedule/detail/detailed/releaseLine`,
diff --git a/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx b/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx
index 2b53dd9..7b63578 100644
--- a/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx
+++ b/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx
@@ -7,6 +7,7 @@ import { EditNote } from "@mui/icons-material";
import { useRouter, useSearchParams } from "next/navigation";
import { useTranslation } from "react-i18next";
import { ScheduleType } from "@/app/api/scheduling";
+import { NEXT_PUBLIC_API_URL } from "@/config/api";
import {
ProdScheduleResult,
SearchProdSchedule,
@@ -14,6 +15,7 @@ import {
fetchProdSchedules,
exportProdSchedule,
testDetailedSchedule,
+ getFile6,
} from "@/app/api/scheduling/actions";
import { defaultPagingController } from "../SearchResults/SearchResults";
import { arrayToDateString, arrayToDayjs, decimalFormatter } from "@/app/utils/formatUtil";
@@ -23,6 +25,9 @@ import { Button, Stack } from "@mui/material";
import isToday from 'dayjs/plugin/isToday';
import useUploadContext from "../UploadProvider/useUploadContext";
import { FileDownload, CalendarMonth } from "@mui/icons-material";
+import { useSession } from "next-auth/react";
+import { VIEW_USER, VIEW_DO } from "@/authorities";
+
dayjs.extend(isToday);
// may need move to "index" or "actions"
@@ -52,6 +57,10 @@ const DSOverview: React.FC = ({ type, defaultInputs }) => {
const { setIsUploading } = useUploadContext();
const today = dayjs().format("YYYY-MM-DD");
+ const { data: session } = useSession();
+ // Extract abilities (safe fallback to empty array if not logged in / no abilities)
+ const abilities = session?.user?.abilities ?? [];
+
const router = useRouter();
// const [filterObj, setFilterObj] = useState({});
// const [tempSelectedValue, setTempSelectedValue] = useState({});
@@ -226,6 +235,48 @@ const DSOverview: React.FC = ({ type, defaultInputs }) => {
refetchData(resetWithToday, "reset"); // Fetch data
}, [defaultInputs, refetchData]);
+ const handleDownloadPrintJob = async () => {
+ const token = localStorage.getItem("accessToken");
+
+ const params = {
+ itemCode: 'TT173',
+ lotNo: 'LOT342989',
+ expiryDate: '2026-02-28',
+ productName: 'Name2342'
+ };
+
+ try {
+ // 1. Direct fetch call to avoid Next.js trying to parse JSON
+ const query = new URLSearchParams(params).toString();
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/get-printer6?${query}`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${token}`
+ }
+ });
+
+ if (!response.ok) throw new Error('Network response was not ok');
+
+ // 2. GET THE DATA AS BLOB (This is the fix)
+ const blob = await response.blob();
+
+ // 3. Create a download link
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', `${params.lotNo}.zip`);
+ document.body.appendChild(link);
+ link.click();
+
+ // Cleanup
+ link.parentNode?.removeChild(link);
+ window.URL.revokeObjectURL(url);
+
+ } catch (error) {
+ console.error("Download failed", error);
+ }
+ };
+
const testDetailedScheduleClick = useCallback(async () => {
try {
setIsUploading(true)
@@ -332,6 +383,21 @@ const DSOverview: React.FC = ({ type, defaultInputs }) => {
>
{t("Export Schedule")}
+
+ {false && abilities.includes(VIEW_USER) && (
+ }
+ onClick={handleDownloadPrintJob}
+ sx={{
+ boxShadow: 2,
+ '&:hover': { backgroundColor: 'success.dark', boxShadow: 4 }
+ }}
+ >
+ Get Printer File API
+
+ )}
{
},
],
},
+ {
+ icon: ,
+ label: "Printer Testing",
+ path: "/testing",
+ isHidden: false,
+ },
{
icon: ,
label: "Settings",
@@ -396,6 +403,7 @@ const NavigationContent: React.FC = () => {
{navigationItems
+ .filter(item => !item.isHidden)
.map(renderNavigationItem)
.filter(Boolean)}
{/* {navigationItems.map(({ icon, label, path }, index) => {