From b0356b7a8a193af4a1cfeade54c128f2541d7959 Mon Sep 17 00:00:00 2001 From: "PC-20260115JRSN\\Administrator" Date: Mon, 23 Feb 2026 11:41:35 +0800 Subject: [PATCH] Fix the files that make project failed to compile --- src/app/(main)/do/edit/page.tsx | 2 +- src/app/(main)/ps/page.tsx | 54 +- src/app/(main)/report/page.tsx | 30 +- .../report/semiFGProductionAnalysisApi.ts | 50 +- src/app/(main)/testing/page.tsx | 36 +- src/app/api/qc/index.ts | 6 +- .../api/settings/m18ImportTesting/actions.ts | 10 +- src/app/api/user/actions.ts | 3 +- src/app/utils/clientAuthFetch.ts | 31 + src/components/CreateUser/CreateUser.tsx | 5 +- .../DetailedScheduleSearchView.tsx | 4 +- src/components/DoSearch/DoSearchWrapper.tsx | 2 +- .../EscalationComponent.tsx | 2 +- .../PickQcStockInModalVer2.tsx | 12 +- .../PickQcStockInModalVer3.tsx | 2 +- .../FinishedGoodSearch/StockInFormVer2.tsx | 4 +- .../FinishedGoodSearch/newcreatitem copy.tsx | 703 ------------------ .../FinishedGoodSearch/pickorderModelVer2.tsx | 7 +- .../InventorySearch/InventorySearch.tsx | 1 + src/components/ItemsSearch/ItemsSearch.tsx | 2 +- .../Jodetail/EscalationComponent.tsx | 2 +- src/components/Jodetail/StockInFormVer2.tsx | 4 +- .../PickOrderSearch/EscalationComponent.tsx | 2 +- src/components/PickOrderSearch/LotTable.tsx | 3 +- .../PickOrderSearch/PickExecution.tsx | 4 +- .../PickQcStockInModalVer2.tsx | 12 +- .../PickOrderSearch/StockInFormVer2.tsx | 4 +- .../PickOrderSearch/pickorderModelVer2.tsx | 7 +- src/components/PoDetail/EscalationTab.tsx | 5 +- src/components/PoDetail/QCDatagrid.tsx | 2 +- src/components/PoDetail/QcFormOld.tsx | 14 +- src/components/PoDetail/QcStockInModal.tsx | 2 +- src/components/PoDetail/QrModal.tsx | 5 + .../ProductionOutputFormPage.tsx | 17 +- src/theme/EmotionCache.tsx | 2 +- tsconfig.json | 2 +- 36 files changed, 173 insertions(+), 880 deletions(-) create mode 100644 src/app/utils/clientAuthFetch.ts diff --git a/src/app/(main)/do/edit/page.tsx b/src/app/(main)/do/edit/page.tsx index a9d8d94..857f3b1 100644 --- a/src/app/(main)/do/edit/page.tsx +++ b/src/app/(main)/do/edit/page.tsx @@ -1,5 +1,5 @@ import { SearchParams } from "@/app/utils/fetchUtil"; -import DoDetail from "@/components/DoDetail/DodetailWrapper"; +import DoDetail from "@/components/DoDetail/DoDetailWrapper"; import { I18nProvider, getServerI18n } from "@/i18n"; import { Typography } from "@mui/material"; import { isArray } from "lodash"; diff --git a/src/app/(main)/ps/page.tsx b/src/app/(main)/ps/page.tsx index 0d74e1c..b008657 100644 --- a/src/app/(main)/ps/page.tsx +++ b/src/app/(main)/ps/page.tsx @@ -12,8 +12,8 @@ import { OnlinePrediction, FileDownload, SettingsEthernet } from "@mui/icons-material"; import dayjs from "dayjs"; -import { redirect } from "next/navigation"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; +import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; export default function ProductionSchedulePage() { // ── Main states ── @@ -69,21 +69,14 @@ export default function ProductionSchedulePage() { // ── API Actions ── const handleSearch = async () => { - const token = localStorage.getItem("accessToken"); setLoading(true); try { - const response = await fetch(`${NEXT_PUBLIC_API_URL}/ps/search-ps?produceAt=${searchDate}`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/ps/search-ps?produceAt=${searchDate}`, { method: 'GET', - headers: { 'Authorization': `Bearer ${token}` } }); - if (response.status === 401 || response.status === 403) { - console.warn(`Auth error ${response.status} → clearing token & redirecting`); - window.location.href = "/login?session=expired"; - - return; // ← stops execution here - } + if (response.status === 401 || response.status === 403) return; const data = await response.json(); @@ -101,7 +94,6 @@ export default function ProductionSchedulePage() { return; } - const token = localStorage.getItem("accessToken"); setLoading(true); setIsForecastDialogOpen(false); @@ -113,10 +105,9 @@ export default function ProductionSchedulePage() { const url = `${NEXT_PUBLIC_API_URL}/productionSchedule/testDetailedSchedule?${params.toString()}`; - const response = await fetch(url, { - method: 'GET', - headers: { 'Authorization': `Bearer ${token}` } - }); + const response = await clientAuthFetch(url, { method: 'GET' }); + + if (response.status === 401 || response.status === 403) return; if (response.ok) { await handleSearch(); // refresh list @@ -140,7 +131,6 @@ export default function ProductionSchedulePage() { return; } - const token = localStorage.getItem("accessToken"); setLoading(true); setIsExportDialogOpen(false); @@ -149,11 +139,11 @@ export default function ProductionSchedulePage() { fromDate: exportFromDate, }); - const response = await fetch(`${NEXT_PUBLIC_API_URL}/productionSchedule/export-prod-schedule?${params.toString()}`, { - method: 'GET', // or keep POST if backend requires it - headers: { 'Authorization': `Bearer ${token}` } + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/productionSchedule/export-prod-schedule?${params.toString()}`, { + method: 'GET', }); + if (response.status === 401 || response.status === 403) return; if (!response.ok) throw new Error(`Export failed: ${response.status}`); const blob = await response.blob(); @@ -183,25 +173,15 @@ export default function ProductionSchedulePage() { return; } - const token = localStorage.getItem("accessToken"); - console.log("Token exists:", !!token); - setSelectedPs(ps); setLoading(true); try { const url = `${NEXT_PUBLIC_API_URL}/ps/search-ps-line?psId=${ps.id}`; - console.log("Sending request to:", url); - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - }, - }); + const response = await clientAuthFetch(url, { method: 'GET' }); - console.log("Response status:", response.status); - console.log("Response ok?", response.ok); + if (response.status === 401 || response.status === 403) return; if (!response.ok) { const errorText = await response.text().catch(() => "(no text)"); @@ -229,19 +209,17 @@ export default function ProductionSchedulePage() { const handleAutoGenJob = async () => { - //if (!isDateToday) return; - const token = localStorage.getItem("accessToken"); + //if (!isDateToday) return; setIsGenerating(true); try { - const response = await fetch(`${NEXT_PUBLIC_API_URL}/productionSchedule/detail/detailed/release`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/productionSchedule/detail/detailed/release`, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: selectedPs.id }) }); + if (response.status === 401 || response.status === 403) return; + if (response.ok) { const data = await response.json(); const displayMessage = data.message || "Operation completed."; diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx index 100c194..f170845 100644 --- a/src/app/(main)/report/page.tsx +++ b/src/app/(main)/report/page.tsx @@ -17,6 +17,7 @@ import { import PrintIcon from '@mui/icons-material/Print'; import { REPORTS, ReportDefinition } from '@/config/reportConfig'; import { NEXT_PUBLIC_API_URL } from '@/config/api'; +import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; import SemiFGProductionAnalysisReport from './SemiFGProductionAnalysisReport'; import { fetchSemiFGItemCodes, @@ -90,25 +91,17 @@ export default function ReportPage() { } // Handle other reports with dynamic options - const token = localStorage.getItem("accessToken"); - - // Handle multiple stockCategory values (comma-separated) - // If "All" is included or no value, fetch all - // Otherwise, fetch for all selected categories let url = field.dynamicOptionsEndpoint; if (paramValue && paramValue !== 'All' && !paramValue.includes('All')) { - // Multiple categories selected (e.g., "FG,WIP") url = `${field.dynamicOptionsEndpoint}?${field.dynamicOptionsParam}=${paramValue}`; } - - const response = await fetch(url, { + + const response = await clientAuthFetch(url, { method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, }); + if (response.status === 401 || response.status === 403) return; if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); @@ -159,21 +152,18 @@ export default function ReportPage() { const executePrint = async () => { if (!currentReport) return; - + setLoading(true); try { - const token = localStorage.getItem("accessToken"); const queryParams = new URLSearchParams(criteria).toString(); const url = `${currentReport.apiEndpoint}?${queryParams}`; - - const response = await fetch(url, { + + const response = await clientAuthFetch(url, { method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/pdf', - }, + headers: { 'Accept': 'application/pdf' }, }); + if (response.status === 401 || response.status === 403) return; if (!response.ok) { const errorText = await response.text(); console.error("Response error:", errorText); diff --git a/src/app/(main)/report/semiFGProductionAnalysisApi.ts b/src/app/(main)/report/semiFGProductionAnalysisApi.ts index 04cfc8a..ba7a1d7 100644 --- a/src/app/(main)/report/semiFGProductionAnalysisApi.ts +++ b/src/app/(main)/report/semiFGProductionAnalysisApi.ts @@ -1,4 +1,7 @@ +"use client"; + import { NEXT_PUBLIC_API_URL } from '@/config/api'; +import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; export interface ItemCodeWithName { code: string; @@ -19,24 +22,18 @@ export interface ItemCodeWithCategory { export const fetchSemiFGItemCodes = async ( stockCategory: string = '' ): Promise => { - const token = localStorage.getItem("accessToken"); - let url = `${NEXT_PUBLIC_API_URL}/report/semi-fg-item-codes`; if (stockCategory && stockCategory !== 'All' && !stockCategory.includes('All')) { url = `${url}?stockCategory=${stockCategory}`; } - - const response = await fetch(url, { + + const response = await clientAuthFetch(url, { method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + if (response.status === 401 || response.status === 403) throw new Error("Unauthorized"); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return await response.json(); }; @@ -49,24 +46,18 @@ export const fetchSemiFGItemCodes = async ( export const fetchSemiFGItemCodesWithCategory = async ( stockCategory: string = '' ): Promise => { - const token = localStorage.getItem("accessToken"); - let url = `${NEXT_PUBLIC_API_URL}/report/semi-fg-item-codes-with-category`; if (stockCategory && stockCategory !== 'All' && !stockCategory.includes('All')) { url = `${url}?stockCategory=${stockCategory}`; } - - const response = await fetch(url, { + + const response = await clientAuthFetch(url, { method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + if (response.status === 401 || response.status === 403) throw new Error("Unauthorized"); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return await response.json(); }; @@ -81,21 +72,16 @@ export const generateSemiFGProductionAnalysisReport = async ( criteria: Record, reportTitle: string = '成品/半成品生產分析報告' ): Promise => { - const token = localStorage.getItem("accessToken"); const queryParams = new URLSearchParams(criteria).toString(); const url = `${NEXT_PUBLIC_API_URL}/report/print-semi-fg-production-analysis?${queryParams}`; - - const response = await fetch(url, { + + const response = await clientAuthFetch(url, { method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/pdf', - }, + headers: { 'Accept': 'application/pdf' }, }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + if (response.status === 401 || response.status === 403) throw new Error("Unauthorized"); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const blob = await response.blob(); const downloadUrl = window.URL.createObjectURL(blob); diff --git a/src/app/(main)/testing/page.tsx b/src/app/(main)/testing/page.tsx index 3efaf70..ae22386 100644 --- a/src/app/(main)/testing/page.tsx +++ b/src/app/(main)/testing/page.tsx @@ -10,6 +10,7 @@ import { import { FileDownload, Print, SettingsEthernet, Lan, Router } from "@mui/icons-material"; import dayjs from "dayjs"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; +import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; // Simple TabPanel component for conditional rendering interface TabPanelProps { @@ -97,14 +98,14 @@ export default function TestingPage() { // 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`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-tsc`, { method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + if (response.status === 401 || response.status === 403) return; if (response.ok) alert(`TSC Print Command Sent for ${row.itemCode}!`); else alert("TSC Print Failed"); } catch (e) { console.error("TSC Error:", e); } @@ -112,14 +113,14 @@ export default function TestingPage() { // 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`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-dataflex`, { method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + if (response.status === 401 || response.status === 403) return; if (response.ok) alert(`DataFlex Print Command Sent for ${row.itemCode}!`); else alert("DataFlex Print Failed"); } catch (e) { console.error("DataFlex Error:", e); } @@ -127,14 +128,13 @@ export default function TestingPage() { // 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()}`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/get-printer6?${params.toString()}`, { method: 'GET', - headers: { 'Authorization': `Bearer ${token}` } }); + if (response.status === 401 || response.status === 403) return; if (!response.ok) throw new Error('Download failed'); const blob = await response.blob(); @@ -153,34 +153,33 @@ export default function TestingPage() { // Laser Print (Section 4 - original) 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`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser`, { method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + if (response.status === 401 || response.status === 403) return; 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 { - const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/preview-laser`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/preview-laser`, { method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + if (response.status === 401 || response.status === 403) return; if (response.ok) alert("Red light preview active!"); } catch (e) { console.error("Preview Error:", e); } }; // HANS600S-M TCP Print (Section 5) const handleHansPrint = async (row: any) => { - const token = localStorage.getItem("accessToken"); const payload = { printerIp: hansConfig.ip, printerPort: hansConfig.port, @@ -190,11 +189,12 @@ export default function TestingPage() { text4ObjectName: row.text4ObjectName }; try { - const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser-tcp`, { + const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser-tcp`, { method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + if (response.status === 401 || response.status === 403) return; const result = await response.text(); if (response.ok) { alert(`HANS600S-M Mark Success: ${result}`); diff --git a/src/app/api/qc/index.ts b/src/app/api/qc/index.ts index b862d63..f079ec5 100644 --- a/src/app/api/qc/index.ts +++ b/src/app/api/qc/index.ts @@ -29,9 +29,9 @@ export interface QcData { name?: string, order?: number, description?: string, - // qcPassed: boolean | undefined - // failQty: number | undefined - // remarks: string | undefined + qcPassed?: boolean, + failQty?: number, + remarks?: string, } export interface QcResult extends QcData{ id?: number; diff --git a/src/app/api/settings/m18ImportTesting/actions.ts b/src/app/api/settings/m18ImportTesting/actions.ts index 4a9bc80..7fbdffc 100644 --- a/src/app/api/settings/m18ImportTesting/actions.ts +++ b/src/app/api/settings/m18ImportTesting/actions.ts @@ -2,7 +2,7 @@ // import { serverFetchWithNoContent } from '@/app/utils/fetchUtil'; // import { BASE_API_URL } from "@/config/api"; -import { serverFetchWithNoContent } from "../../../utils/fetchUtil"; +import { serverFetch, serverFetchWithNoContent } from "../../../utils/fetchUtil"; import { BASE_API_URL } from "../../../../config/api"; export interface M18ImportPoForm { @@ -85,13 +85,13 @@ export const triggerScheduler = async (type: 'po' | 'do1' | 'do2' | 'master-data console.log("Fetching URL:", url); - const response = await serverFetchWithNoContent(url, { + const response = await serverFetch(url, { method: "GET", cache: "no-store", }); if (!response.ok) throw new Error(`Failed: ${response.status}`); - + return await response.text(); } catch (error) { console.error("Scheduler Action Error:", error); @@ -103,13 +103,13 @@ export const refreshCronSchedules = async () => { // Simply reuse the triggerScheduler logic to avoid duplication // or call serverFetch directly as shown below: try { - const response = await serverFetchWithNoContent(`${BASE_API_URL}/scheduler/refresh-cron`, { + const response = await serverFetch(`${BASE_API_URL}/scheduler/refresh-cron`, { method: "GET", cache: "no-store", }); if (!response.ok) throw new Error(`Failed to refresh: ${response.status}`); - + return await response.text(); } catch (error) { console.error("Refresh Cron Error:", error); diff --git a/src/app/api/user/actions.ts b/src/app/api/user/actions.ts index 1fb5495..66c2ef4 100644 --- a/src/app/api/user/actions.ts +++ b/src/app/api/user/actions.ts @@ -13,10 +13,11 @@ export interface UserInputs { username: string; name: string; staffNo?: string; + locked?: boolean; addAuthIds?: number[]; removeAuthIds?: number[]; password?: string; - confirmPassword?: string; + confirmPassword?: string; } export interface PasswordInputs { diff --git a/src/app/utils/clientAuthFetch.ts b/src/app/utils/clientAuthFetch.ts new file mode 100644 index 0000000..1bc8462 --- /dev/null +++ b/src/app/utils/clientAuthFetch.ts @@ -0,0 +1,31 @@ +"use client"; + +const LOGIN_REDIRECT = "/login?session=expired"; + +/** + * Client-side fetch that adds Bearer token from localStorage and redirects + * to /login?session=expired on 401 or 403 (session timeout / unauthorized). + * Use this for all authenticated API requests so session expiry is handled consistently. + */ +export async function clientAuthFetch( + input: RequestInfo | URL, + init?: RequestInit +): Promise { + const token = + typeof window !== "undefined" ? localStorage.getItem("accessToken") : null; + const headers = new Headers(init?.headers); + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + + const response = await fetch(input, { ...init, headers }); + + if (response.status === 401 || response.status === 403) { + if (typeof window !== "undefined") { + console.warn(`Auth error ${response.status} → redirecting to login`); + window.location.href = LOGIN_REDIRECT; + } + } + + return response; +} diff --git a/src/components/CreateUser/CreateUser.tsx b/src/components/CreateUser/CreateUser.tsx index 4b6ea25..33b9e79 100644 --- a/src/components/CreateUser/CreateUser.tsx +++ b/src/components/CreateUser/CreateUser.tsx @@ -143,9 +143,10 @@ const CreateUser: React.FC = ({ rules, auths }) => { }); } } - const userData = { + const userData: UserInputs = { username: data.username, - // name: data.name, + name: data.name ?? "", + staffNo: data.staffNo, locked: false, addAuthIds: data.addAuthIds || [], removeAuthIds: data.removeAuthIds || [], diff --git a/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx b/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx index ddccc5a..6c32223 100644 --- a/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx +++ b/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx @@ -26,7 +26,7 @@ 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 } from "@/authorities"; +import { AUTH } from "@/authorities"; dayjs.extend(isToday); @@ -384,7 +384,7 @@ const DSOverview: React.FC = ({ type, defaultInputs }) => { {t("Export Schedule")} - {false && abilities.includes(VIEW_USER) && ( + {false && abilities.includes(AUTH.VIEW_USER) && (