Просмотр исходного кода

Fix the files that make project failed to compile

reset-do-picking-order
PC-20260115JRSN\Administrator 3 недель назад
Родитель
Сommit
b0356b7a8a
36 измененных файлов: 173 добавлений и 880 удалений
  1. +1
    -1
      src/app/(main)/do/edit/page.tsx
  2. +16
    -38
      src/app/(main)/ps/page.tsx
  3. +10
    -20
      src/app/(main)/report/page.tsx
  4. +18
    -32
      src/app/(main)/report/semiFGProductionAnalysisApi.ts
  5. +18
    -18
      src/app/(main)/testing/page.tsx
  6. +3
    -3
      src/app/api/qc/index.ts
  7. +5
    -5
      src/app/api/settings/m18ImportTesting/actions.ts
  8. +2
    -1
      src/app/api/user/actions.ts
  9. +31
    -0
      src/app/utils/clientAuthFetch.ts
  10. +3
    -2
      src/components/CreateUser/CreateUser.tsx
  11. +2
    -2
      src/components/DetailedSchedule/DetailedScheduleSearchView.tsx
  12. +1
    -1
      src/components/DoSearch/DoSearchWrapper.tsx
  13. +1
    -1
      src/components/FinishedGoodSearch/EscalationComponent.tsx
  14. +6
    -6
      src/components/FinishedGoodSearch/PickQcStockInModalVer2.tsx
  15. +1
    -1
      src/components/FinishedGoodSearch/PickQcStockInModalVer3.tsx
  16. +2
    -2
      src/components/FinishedGoodSearch/StockInFormVer2.tsx
  17. +0
    -703
      src/components/FinishedGoodSearch/newcreatitem copy.tsx
  18. +4
    -3
      src/components/FinishedGoodSearch/pickorderModelVer2.tsx
  19. +1
    -0
      src/components/InventorySearch/InventorySearch.tsx
  20. +1
    -1
      src/components/ItemsSearch/ItemsSearch.tsx
  21. +1
    -1
      src/components/Jodetail/EscalationComponent.tsx
  22. +2
    -2
      src/components/Jodetail/StockInFormVer2.tsx
  23. +1
    -1
      src/components/PickOrderSearch/EscalationComponent.tsx
  24. +2
    -1
      src/components/PickOrderSearch/LotTable.tsx
  25. +2
    -2
      src/components/PickOrderSearch/PickExecution.tsx
  26. +6
    -6
      src/components/PickOrderSearch/PickQcStockInModalVer2.tsx
  27. +2
    -2
      src/components/PickOrderSearch/StockInFormVer2.tsx
  28. +4
    -3
      src/components/PickOrderSearch/pickorderModelVer2.tsx
  29. +3
    -2
      src/components/PoDetail/EscalationTab.tsx
  30. +1
    -1
      src/components/PoDetail/QCDatagrid.tsx
  31. +7
    -7
      src/components/PoDetail/QcFormOld.tsx
  32. +1
    -1
      src/components/PoDetail/QcStockInModal.tsx
  33. +5
    -0
      src/components/PoDetail/QrModal.tsx
  34. +8
    -9
      src/components/ProductionProcess/ProductionOutputFormPage.tsx
  35. +1
    -1
      src/theme/EmotionCache.tsx
  36. +1
    -1
      tsconfig.json

+ 1
- 1
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";


+ 16
- 38
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.";


+ 10
- 20
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);


+ 18
- 32
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<ItemCodeWithName[]> => {
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<ItemCodeWithCategory[]> => {
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<string, string>,
reportTitle: string = '成品/半成品生產分析報告'
): Promise<void> => {
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);


+ 18
- 18
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}`);


+ 3
- 3
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;


+ 5
- 5
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);


+ 2
- 1
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 {


+ 31
- 0
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<Response> {
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;
}

+ 3
- 2
src/components/CreateUser/CreateUser.tsx Просмотреть файл

@@ -143,9 +143,10 @@ const CreateUser: React.FC<Props> = ({ 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 || [],


+ 2
- 2
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<Props> = ({ type, defaultInputs }) => {
{t("Export Schedule")}
</Button>

{false && abilities.includes(VIEW_USER) && (
{false && abilities.includes(AUTH.VIEW_USER) && (
<Button
variant="contained" // Solid button for the "Export" action
color="success" // Green color often signifies a successful action/download


+ 1
- 1
src/components/DoSearch/DoSearchWrapper.tsx Просмотреть файл

@@ -9,7 +9,7 @@ interface SubComponents {

const DoSearchWrapper: React.FC & SubComponents = async () => {
// const [dos] = await Promise.all([fetchDoList()]);
return <DoSearch />;
return <DoSearch onDeliveryOrderSearch={() => {}} />;
};

DoSearchWrapper.Loading = GeneralLoading;


+ 1
- 1
src/components/FinishedGoodSearch/EscalationComponent.tsx Просмотреть файл

@@ -61,7 +61,7 @@ const EscalationComponent: React.FC<Props> = ({
];

const handleInputChange = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SelectChangeEvent<string>
): void => {
const { name, value } = event.target;
setFormData((prev) => ({


+ 6
- 6
src/components/FinishedGoodSearch/PickQcStockInModalVer2.tsx Просмотреть файл

@@ -22,7 +22,7 @@ import {
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { dummyQCData, QcData } from "../PoDetail/dummyQcTemplate";
import { dummyQCData, QcData } from "./dummyQcTemplate";
import { submitDialogWithWarning } from "../Swal/CustomAlerts";

const style = {
@@ -149,17 +149,17 @@ const PickQcStockInModalVer2: React.FC<Props> = ({
if (!qcData.qcItems.every((qc) => qc.isPassed) && qcData.qcAccept) {
submitDialogWithWarning(() => {
console.log("QC accepted with failed items");
onClose();
onClose?.({} as object, "backdropClick");
}, t, {title:"有不合格檢查項目,確認接受收貨?", confirmButtonText: "Confirm", html: ""});
return;
}

if (qcData.qcAccept) {
console.log("QC accepted");
onClose();
onClose?.({} as object, "backdropClick");
} else {
console.log("QC rejected");
onClose();
onClose?.({} as object, "backdropClick");
}
},
[qcItems, onClose, t],
@@ -260,7 +260,7 @@ const PickQcStockInModalVer2: React.FC<Props> = ({
color="warning"
onClick={() => {
console.log("Sort to accept");
onClose();
onClose?.({} as object, "backdropClick");
}}
>
Sort to Accept
@@ -270,7 +270,7 @@ const PickQcStockInModalVer2: React.FC<Props> = ({
color="error"
onClick={() => {
console.log("Reject and pick another lot");
onClose();
onClose?.({} as object, "backdropClick");
}}
>
Reject and Pick Another Lot


+ 1
- 1
src/components/FinishedGoodSearch/PickQcStockInModalVer3.tsx Просмотреть файл

@@ -24,7 +24,7 @@ import {
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { dummyQCData } from "../PoDetail/dummyQcTemplate";
import { dummyQCData } from "./dummyQcTemplate";
import StyledDataGrid from "../StyledDataGrid";
import { GridColDef } from "@mui/x-data-grid";
import { submitDialogWithWarning } from "../Swal/CustomAlerts";


+ 2
- 2
src/components/FinishedGoodSearch/StockInFormVer2.tsx Просмотреть файл

@@ -111,7 +111,7 @@ const StockInFormVer2: React.FC<Props> = ({
if (isPickOrderData) {
// PickOrder 数据
const pickOrderItem = itemDetail as GetPickOrderLineInfo & { pickOrderCode: string };
return pickOrderItem.uomDesc || pickOrderItem.uomCode || '';
return pickOrderItem.uomDesc || (pickOrderItem as { uomCode?: string }).uomCode || '';
} else {
// StockIn 数据
const stockInItem = itemDetail as StockInLine;
@@ -169,7 +169,7 @@ const StockInFormVer2: React.FC<Props> = ({
<TextField
label={t("itemNo")}
fullWidth
{...register("itemNo", {
{...(register as (name: string, opts?: object) => ReturnType<typeof register>)("itemNo", {
required: "itemNo required!",
})}
value={getItemDisplayValue()}


+ 0
- 703
src/components/FinishedGoodSearch/newcreatitem copy.tsx Просмотреть файл

@@ -1346,709 +1346,6 @@ const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCr
setSearchResultsPagingController(newPagingController);
}, []);

// Add pagination state for created items
const [createdItemsPagingController, setCreatedItemsPagingController] = useState({
pageNum: 1,
pageSize: 10,
});

// Add pagination handlers for created items
const handleCreatedItemsPageChange = useCallback((event: unknown, newPage: number) => {
const newPagingController = {
...createdItemsPagingController,
pageNum: newPage + 1,
};
setCreatedItemsPagingController(newPagingController);
}, [createdItemsPagingController]);

const handleCreatedItemsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newPageSize = parseInt(event.target.value, 10);
const newPagingController = {
pageNum: 1,
pageSize: newPageSize,
};
setCreatedItemsPagingController(newPagingController);
}, []);

// Create a custom table for created items with pagination
const CustomCreatedItemsTable = () => {
const startIndex = (createdItemsPagingController.pageNum - 1) * createdItemsPagingController.pageSize;
const endIndex = startIndex + createdItemsPagingController.pageSize;
const paginatedCreatedItems = createdItems.slice(startIndex, endIndex);

return (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
{t("Selected")}
</TableCell>
<TableCell>
{t("Item")}
</TableCell>
<TableCell>
{t("Group")}
</TableCell>
<TableCell align="right">
{t("Current Stock")}
</TableCell>
<TableCell align="right">
{t("Stock Unit")}
</TableCell>
<TableCell align="right">
{t("Order Quantity")}
</TableCell>
<TableCell align="right">
{t("Target Date")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{paginatedCreatedItems.length === 0 ? (
<TableRow>
<TableCell colSpan={12} align="center">
<Typography variant="body2" color="text.secondary">
{t("No created items")}
</Typography>
</TableCell>
</TableRow>
) : (
paginatedCreatedItems.map((item) => (
<TableRow key={item.itemId}>
<TableCell padding="checkbox">
<Checkbox
checked={item.isSelected}
onChange={(e) => handleCreatedItemSelect(item.itemId, e.target.checked)}
/>
</TableCell>
<TableCell>
<Typography variant="body2">{item.itemName}</Typography>
<Typography variant="caption" color="textSecondary">
{item.itemCode}
</Typography>
</TableCell>
<TableCell>
<FormControl size="small" sx={{ minWidth: 120 }}>
<Select
value={item.groupId?.toString() || ""}
onChange={(e) => handleCreatedItemGroupChange(item.itemId, e.target.value)}
displayEmpty
>
<MenuItem value="">
<em>{t("No Group")}</em>
</MenuItem>
{groups.map((group) => (
<MenuItem key={group.id} value={group.id.toString()}>
{group.name}
</MenuItem>
))}
</Select>
</FormControl>
</TableCell>
<TableCell align="right">
<Typography
variant="body2"
color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
>
{item.currentStockBalance?.toLocaleString() || 0}
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="body2">{item.uomDesc}</Typography>
</TableCell>
<TableCell align="right">
<TextField
type="number"
size="small"
value={item.qty || ""}
onChange={(e) => {
const newQty = Number(e.target.value);
handleQtyChange(item.itemId, newQty);
}}
inputProps={{
min: 1,
step: 1,
style: { textAlign: 'center' }
}}
sx={{
width: '80px',
'& .MuiInputBase-input': {
textAlign: 'center',
cursor: 'text'
}
}}
/>
</TableCell>
<TableCell align="right">
<Typography variant="body2">
{item.targetDate ? new Date(item.targetDate).toLocaleDateString() : "-"}
</Typography>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{/* Pagination for created items */}
<TablePagination
component="div"
count={createdItems.length}
page={(createdItemsPagingController.pageNum - 1)}
rowsPerPage={createdItemsPagingController.pageSize}
onPageChange={handleCreatedItemsPageChange}
onRowsPerPageChange={handleCreatedItemsPageSizeChange}
rowsPerPageOptions={[10, 25, 50]}
labelRowsPerPage={t("Rows per page")}
labelDisplayedRows={({ from, to, count }) =>
`${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
}
/>
</>
);
};

// Define columns for SearchResults
const searchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
{
name: "id",
label: "",
type: "checkbox",
disabled: (item) => isItemInCreated(item.id), // Disable if already in created items
},

{
name: "label",
label: t("Item"),
renderCell: (item) => {
const parts = item.label.split(' - ');
const code = parts[0] || '';
const name = parts[1] || '';
return (
<Box>
<Typography variant="body2">
{name} {/* 显示项目名称 */}
</Typography>
<Typography variant="caption" color="textSecondary">
{code} {/* 显示项目代码 */}
</Typography>
</Box>
);
},
},
{
name: "qty",
label: t("Order Quantity"),
renderCell: (item) => (
<TextField
type="number"
size="small"
value={item.qty || ""} // Show empty string if qty is null
onChange={(e) => {
const value = e.target.value;
const numValue = value === "" ? null : Number(value);
handleSearchQtyChange(item.id, numValue);
}}
inputProps={{
min: 1,
step: 1,
style: { textAlign: 'center' } // Center the text
}}
sx={{
width: '80px',
'& .MuiInputBase-input': {
textAlign: 'center',
cursor: 'text'
}
}}
/>
),
},
{
name: "currentStockBalance",
label: t("Current Stock"),
renderCell: (item) => {
const stockBalance = item.currentStockBalance || 0;
return (
<Typography
variant="body2"
color={stockBalance > 0 ? "success.main" : "error.main"}
sx={{ fontWeight: stockBalance > 0 ? 'bold' : 'normal' }}
>
{stockBalance}
</Typography>
);
},
},
{
name: "targetDate",
label: t("Target Date"),
renderCell: (item) => (
<Typography variant="body2">
{item.targetDate ? new Date(item.targetDate).toLocaleDateString() : "-"}
</Typography>
),
},
{
name: "uom",
label: t("Stock Unit"),
renderCell: (item) => item.uom || "-",
},
], [t, isItemInCreated, handleSearchQtyChange]);
// 修改搜索条件为3行,每行一个 - 确保SearchBox组件能正确处理
const pickOrderSearchCriteria: Criterion<any>[] = useMemo(
() => [


{
label: t("Item Code"),
paramName: "code",
type: "text"
},
{
label: t("Item Name"),
paramName: "name",
type: "text"
},
{
label: t("Product Type"),
paramName: "type",
type: "autocomplete",
options: [
{ value: "Consumable", label: t("Consumable") },
{ value: "MATERIAL", label: t("Material") },
{ value: "End_product", label: t("End Product") }
],
},
],
[t],
);

// 添加重置函数
const handleSecondReset = useCallback(() => {
console.log("Second search reset");
setSecondSearchQuery({});
setSecondSearchResults([]);
setHasSearchedSecond(false);
// 清空表单中的类型,但保留今天的日期
formProps.setValue("type", "");
const today = dayjs().format(INPUT_DATE_FORMAT);
formProps.setValue("targetDate", today);
}, [formProps]);

// 添加数量变更处理函数
const handleSecondSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
setSecondSearchResults(prev =>
prev.map(item =>
item.id === itemId ? { ...item, qty: newQty } : item
)
);
// Auto-update created items if this item exists there
setCreatedItems(prev =>
prev.map(item =>
item.itemId === itemId ? { ...item, qty: newQty || 1 } : item
)
);
}, []);

// Add checkbox change handler for second search
const handleSecondSearchCheckboxChange = useCallback((ids: (string | number)[] | ((prev: (string | number)[]) => (string | number)[])) => {
if (typeof ids === 'function') {
const newIds = ids(selectedSecondSearchItemIds);
setSelectedSecondSearchItemIds(newIds);
// 处理全选逻辑 - 选择所有搜索结果,不仅仅是当前页面
if (newIds.length === secondSearchResults.length) {
// 全选:将所有搜索结果添加到创建项目
secondSearchResults.forEach(item => {
if (!isItemInCreated(item.id)) {
handleSecondSearchItemSelect(item.id, true);
}
});
} else {
// 部分选择:只处理当前页面的选择
secondSearchResults.forEach(item => {
const isSelected = newIds.includes(item.id);
const isCurrentlyInCreated = isItemInCreated(item.id);
if (isSelected && !isCurrentlyInCreated) {
handleSecondSearchItemSelect(item.id, true);
} else if (!isSelected && isCurrentlyInCreated) {
setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== item.id));
}
});
}
} else {
const previousIds = selectedSecondSearchItemIds;
setSelectedSecondSearchItemIds(ids);
const newlySelected = ids.filter(id => !previousIds.includes(id));
const newlyDeselected = previousIds.filter(id => !ids.includes(id));
newlySelected.forEach(id => {
if (!isItemInCreated(id as number)) {
handleSecondSearchItemSelect(id as number, true);
}
});
newlyDeselected.forEach(id => {
setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== id));
});
}
}, [selectedSecondSearchItemIds, secondSearchResults, isItemInCreated, handleSecondSearchItemSelect]);

// Update the secondSearchItemColumns to add right alignment for Current Stock and Order Quantity
const secondSearchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
{
name: "id",
label: "",
type: "checkbox",
disabled: (item) => isItemInCreated(item.id),
},
{
name: "label",
label: t("Item"),
renderCell: (item) => {
const parts = item.label.split(' - ');
const code = parts[0] || '';
const name = parts[1] || '';
return (
<Box>
<Typography variant="body2">
{name}
</Typography>
<Typography variant="caption" color="textSecondary">
{code}
</Typography>
</Box>
);
},
},
{
name: "currentStockBalance",
label: t("Current Stock"),
align: "right", // Add right alignment for the label
renderCell: (item) => {
const stockBalance = item.currentStockBalance || 0;
return (
<Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
<Typography
variant="body2"
color={stockBalance > 0 ? "success.main" : "error.main"}
sx={{
fontWeight: stockBalance > 0 ? 'bold' : 'normal',
textAlign: 'right' // Add right alignment for the value
}}
>
{stockBalance}
</Typography>
</Box>
);
},
},
{
name: "uom",
label: t("Stock Unit"),
align: "right", // Add right alignment for the label
renderCell: (item) => (
<Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
<Typography sx={{ textAlign: 'right' }}> {/* Add right alignment for the value */}
{item.uom || "-"}
</Typography>
</Box>
),
},
{
name: "qty",
label: t("Order Quantity"),
align: "right",
renderCell: (item) => (
<Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
<TextField
type="number"
size="small"
value={item.qty || ""}
onChange={(e) => {
const value = e.target.value;
// Only allow numbers
if (value === "" || /^\d+$/.test(value)) {
const numValue = value === "" ? null : Number(value);
handleSecondSearchQtyChange(item.id, numValue);
}
}}
inputProps={{
style: { textAlign: 'center' }
}}
sx={{
width: '80px',
'& .MuiInputBase-input': {
textAlign: 'center',
cursor: 'text'
}
}}
onBlur={(e) => {
const value = e.target.value;
const numValue = value === "" ? null : Number(value);
if (numValue !== null && numValue < 1) {
handleSecondSearchQtyChange(item.id, 1); // Enforce min value
}
}}
/>
</Box>
),
}
], [t, isItemInCreated, handleSecondSearchQtyChange, groups]);

// 添加缺失的 handleSecondSearch 函数
const handleSecondSearch = useCallback((query: Record<string, any>) => {
console.log("Second search triggered with query:", query);
setSecondSearchQuery({ ...query });
setIsLoadingSecondSearch(true);
// Sync second search box info to form - ensure type value is correct
if (query.type) {
// Ensure type value matches backend enum format
let correctType = query.type;
if (query.type === "consumable") {
correctType = "Consumable";
} else if (query.type === "material") {
correctType = "MATERIAL";
} else if (query.type === "jo") {
correctType = "JOB_ORDER";
}
formProps.setValue("type", correctType);
}
setTimeout(() => {
let filtered = items;
// Same filtering logic as first search
if (query.code && query.code.trim()) {
filtered = filtered.filter(item =>
item.label.toLowerCase().includes(query.code.toLowerCase())
);
}
if (query.name && query.name.trim()) {
filtered = filtered.filter(item =>
item.label.toLowerCase().includes(query.name.toLowerCase())
);
}
if (query.type && query.type !== "All") {
// Filter by type if needed
}
// Convert to SearchItemWithQty with NO group/targetDate initially
const filteredWithQty = filtered.slice(0, 100).map(item => ({
...item,
qty: null,
targetDate: undefined, // No target date initially
groupId: undefined, // No group initially
}));
setSecondSearchResults(filteredWithQty);
setHasSearchedSecond(true);
setIsLoadingSecondSearch(false);
}, 500);
}, [items, formProps]);

// Add pagination state for search results
const [searchResultsPagingController, setSearchResultsPagingController] = useState({
pageNum: 1,
pageSize: 10,
});

// Add pagination handlers for search results
const handleSearchResultsPageChange = useCallback((event: unknown, newPage: number) => {
const newPagingController = {
...searchResultsPagingController,
pageNum: newPage + 1, // API uses 1-based pagination
};
setSearchResultsPagingController(newPagingController);
}, [searchResultsPagingController]);

const handleSearchResultsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newPageSize = parseInt(event.target.value, 10);
const newPagingController = {
pageNum: 1, // Reset to first page
pageSize: newPageSize,
};
setSearchResultsPagingController(newPagingController);
}, []);

// Add pagination state for created items
const [createdItemsPagingController, setCreatedItemsPagingController] = useState({
pageNum: 1,
pageSize: 10,
});

// Add pagination handlers for created items
const handleCreatedItemsPageChange = useCallback((event: unknown, newPage: number) => {
const newPagingController = {
...createdItemsPagingController,
pageNum: newPage + 1,
};
setCreatedItemsPagingController(newPagingController);
}, [createdItemsPagingController]);

const handleCreatedItemsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newPageSize = parseInt(event.target.value, 10);
const newPagingController = {
pageNum: 1,
pageSize: newPageSize,
};
setCreatedItemsPagingController(newPagingController);
}, []);

// Create a custom table for created items with pagination
const CustomCreatedItemsTable = () => {
const startIndex = (createdItemsPagingController.pageNum - 1) * createdItemsPagingController.pageSize;
const endIndex = startIndex + createdItemsPagingController.pageSize;
const paginatedCreatedItems = createdItems.slice(startIndex, endIndex);

return (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
{t("Selected")}
</TableCell>
<TableCell>
{t("Item")}
</TableCell>
<TableCell>
{t("Group")}
</TableCell>
<TableCell align="right">
{t("Current Stock")}
</TableCell>
<TableCell align="right">
{t("Stock Unit")}
</TableCell>
<TableCell align="right">
{t("Order Quantity")}
</TableCell>
<TableCell align="right">
{t("Target Date")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{paginatedCreatedItems.length === 0 ? (
<TableRow>
<TableCell colSpan={12} align="center">
<Typography variant="body2" color="text.secondary">
{t("No created items")}
</Typography>
</TableCell>
</TableRow>
) : (
paginatedCreatedItems.map((item) => (
<TableRow key={item.itemId}>
<TableCell padding="checkbox">
<Checkbox
checked={item.isSelected}
onChange={(e) => handleCreatedItemSelect(item.itemId, e.target.checked)}
/>
</TableCell>
<TableCell>
<Typography variant="body2">{item.itemName}</Typography>
<Typography variant="caption" color="textSecondary">
{item.itemCode}
</Typography>
</TableCell>
<TableCell>
<FormControl size="small" sx={{ minWidth: 120 }}>
<Select
value={item.groupId?.toString() || ""}
onChange={(e) => handleCreatedItemGroupChange(item.itemId, e.target.value)}
displayEmpty
>
<MenuItem value="">
<em>{t("No Group")}</em>
</MenuItem>
{groups.map((group) => (
<MenuItem key={group.id} value={group.id.toString()}>
{group.name}
</MenuItem>
))}
</Select>
</FormControl>
</TableCell>
<TableCell align="right">
<Typography
variant="body2"
color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
>
{item.currentStockBalance?.toLocaleString() || 0}
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="body2">{item.uomDesc}</Typography>
</TableCell>
<TableCell align="right">
<TextField
type="number"
size="small"
value={item.qty || ""}
onChange={(e) => {
const newQty = Number(e.target.value);
handleQtyChange(item.itemId, newQty);
}}
inputProps={{
min: 1,
step: 1,
style: { textAlign: 'center' }
}}
sx={{
width: '80px',
'& .MuiInputBase-input': {
textAlign: 'center',
cursor: 'text'
}
}}
/>
</TableCell>
<TableCell align="right">
<Typography variant="body2">
{item.targetDate ? new Date(item.targetDate).toLocaleDateString() : "-"}
</Typography>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{/* Pagination for created items */}
<TablePagination
component="div"
count={createdItems.length}
page={(createdItemsPagingController.pageNum - 1)}
rowsPerPage={createdItemsPagingController.pageSize}
onPageChange={handleCreatedItemsPageChange}
onRowsPerPageChange={handleCreatedItemsPageSizeChange}
rowsPerPageOptions={[10, 25, 50]}
labelRowsPerPage={t("Rows per page")}
labelDisplayedRows={({ from, to, count }) =>
`${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
}
/>
</>
);
};

// Add helper function to get group range text
const getGroupRangeText = useCallback(() => {
if (groups.length === 0) return "";


+ 4
- 3
src/components/FinishedGoodSearch/pickorderModelVer2.tsx Просмотреть файл

@@ -3,6 +3,7 @@
import { GetPickOrderLineInfo } from "@/app/api/pickOrder/actions";
import { QcItemWithChecks } from "@/app/api/qc";
import { PurchaseQcResult } from "@/app/api/po/actions";
import { StockInLine } from "@/app/api/po";
import {
Box,
Button,
@@ -187,7 +188,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
if (qcData.qcAccept) {
onOpenPutaway();
} else {
onClose();
onClose?.({} as object, "backdropClick");
}
},
[onOpenPutaway, qcItems],
@@ -281,7 +282,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
onSubmit={formProps.handleSubmit(onSubmitPutaway)}
>
<PutawayForm
itemDetail={itemDetail}
itemDetail={itemDetail as unknown as StockInLine}
warehouse={warehouse!}
disabled={false}
/>
@@ -341,7 +342,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
>
<QcFormVer2
qc={qc!}
itemDetail={itemDetail}
itemDetail={itemDetail as unknown as StockInLine}
disabled={false}
qcItems={qcItems}
setQcItems={setQcItems}


+ 1
- 0
src/components/InventorySearch/InventorySearch.tsx Просмотреть файл

@@ -58,6 +58,7 @@ const InventorySearch: React.FC<Props> = ({ inventories }) => {
currencyName: "",
status: "",
baseUom: "",
uomShortDesc: "",
}), [])
const [inputs, setInputs] = useState<Record<SearchParamNames, string>>(defaultInputs);



+ 1
- 1
src/components/ItemsSearch/ItemsSearch.tsx Просмотреть файл

@@ -195,7 +195,7 @@ const ItemsSearch: React.FC<Props> = ({ items }) => {
setFilterObj({
...query,
});
refetchData(query);
refetchData(query as unknown as SearchQuery);
}}
onReset={onReset}
/>


+ 1
- 1
src/components/Jodetail/EscalationComponent.tsx Просмотреть файл

@@ -61,7 +61,7 @@ const EscalationComponent: React.FC<Props> = ({
];

const handleInputChange = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SelectChangeEvent<string>
): void => {
const { name, value } = event.target;
setFormData((prev) => ({


+ 2
- 2
src/components/Jodetail/StockInFormVer2.tsx Просмотреть файл

@@ -111,7 +111,7 @@ const StockInFormVer2: React.FC<Props> = ({
if (isPickOrderData) {
// PickOrder 数据
const pickOrderItem = itemDetail as GetPickOrderLineInfo & { pickOrderCode: string };
return pickOrderItem.uomDesc || pickOrderItem.uomCode || '';
return pickOrderItem.uomDesc || (pickOrderItem as { uomCode?: string }).uomCode || '';
} else {
// StockIn 数据
const stockInItem = itemDetail as StockInLine;
@@ -169,7 +169,7 @@ const StockInFormVer2: React.FC<Props> = ({
<TextField
label={t("itemNo")}
fullWidth
{...register("itemNo", {
{...(register as (name: string, opts?: object) => ReturnType<typeof register>)("itemNo", {
required: "itemNo required!",
})}
value={getItemDisplayValue()}


+ 1
- 1
src/components/PickOrderSearch/EscalationComponent.tsx Просмотреть файл

@@ -61,7 +61,7 @@ const EscalationComponent: React.FC<Props> = ({
];

const handleInputChange = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SelectChangeEvent<string>
): void => {
const { name, value } = event.target;
setFormData((prev) => ({


+ 2
- 1
src/components/PickOrderSearch/LotTable.tsx Просмотреть файл

@@ -75,7 +75,8 @@ interface LotTableProps {
selectedLotForInput: LotPickData | null;
generateInputBody: () => any;
onDataRefresh: () => Promise<void>;
onLotDataRefresh: () => Promise<void>;
onLotDataRefresh: () => Promise<void>;
onIssueNoLotStockOutLine?: (stockOutLineId: number) => void | Promise<void>;
}

// QR Code Modal Component


+ 2
- 2
src/components/PickOrderSearch/PickExecution.tsx Просмотреть файл

@@ -964,7 +964,7 @@ const handleIssueNoLotStockOutLine = useCallback(async (stockOutLineId: number)
{lotData.length > 0 ? (
<LotTable
lotData={lotData}
lotData={lotData as Parameters<typeof LotTable>[0]["lotData"]}
selectedRowId={selectedRowId}
selectedRow={selectedRow}
pickQtyData={pickQtyData}
@@ -981,7 +981,7 @@ const handleIssueNoLotStockOutLine = useCallback(async (stockOutLineId: number)
showInputBody={showInputBody}
onIssueNoLotStockOutLine={handleIssueNoLotStockOutLine}
setShowInputBody={setShowInputBody}
//selectedLotForInput={selectedLotForInput}
selectedLotForInput={selectedLotForInput as Parameters<typeof LotTable>[0]["selectedLotForInput"]}
generateInputBody={generateInputBody}
// Add missing props
totalPickedByAllPickOrders={0} // You can calculate this from lotData if needed


+ 6
- 6
src/components/PickOrderSearch/PickQcStockInModalVer2.tsx Просмотреть файл

@@ -22,7 +22,7 @@ import {
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { dummyQCData, QcData } from "../PoDetail/dummyQcTemplate";
import { dummyQCData, QcData } from "./dummyQcTemplate";
import { submitDialogWithWarning } from "../Swal/CustomAlerts";

const style = {
@@ -149,17 +149,17 @@ const PickQcStockInModalVer2: React.FC<Props> = ({
if (!qcData.qcItems.every((qc) => qc.isPassed) && qcData.qcAccept) {
submitDialogWithWarning(() => {
console.log("QC accepted with failed items");
onClose();
onClose?.({} as object, "backdropClick");
}, t, {title:"有不合格檢查項目,確認接受收貨?", confirmButtonText: "Confirm", html: ""});
return;
}

if (qcData.qcAccept) {
console.log("QC accepted");
onClose();
onClose?.({} as object, "backdropClick");
} else {
console.log("QC rejected");
onClose();
onClose?.({} as object, "backdropClick");
}
},
[qcItems, onClose, t],
@@ -260,7 +260,7 @@ const PickQcStockInModalVer2: React.FC<Props> = ({
color="warning"
onClick={() => {
console.log("Sort to accept");
onClose();
onClose?.({} as object, "backdropClick");
}}
>
Sort to Accept
@@ -270,7 +270,7 @@ const PickQcStockInModalVer2: React.FC<Props> = ({
color="error"
onClick={() => {
console.log("Reject and pick another lot");
onClose();
onClose?.({} as object, "backdropClick");
}}
>
Reject and Pick Another Lot


+ 2
- 2
src/components/PickOrderSearch/StockInFormVer2.tsx Просмотреть файл

@@ -111,7 +111,7 @@ const StockInFormVer2: React.FC<Props> = ({
if (isPickOrderData) {
// PickOrder 数据
const pickOrderItem = itemDetail as GetPickOrderLineInfo & { pickOrderCode: string };
return pickOrderItem.uomDesc || pickOrderItem.uomCode || '';
return pickOrderItem.uomDesc || (pickOrderItem as { uomCode?: string }).uomCode || '';
} else {
// StockIn 数据
const stockInItem = itemDetail as StockInLine;
@@ -169,7 +169,7 @@ const StockInFormVer2: React.FC<Props> = ({
<TextField
label={t("itemNo")}
fullWidth
{...register("itemNo", {
{...(register as (name: string, opts?: object) => ReturnType<typeof register>)("itemNo", {
required: "itemNo required!",
})}
value={getItemDisplayValue()}


+ 4
- 3
src/components/PickOrderSearch/pickorderModelVer2.tsx Просмотреть файл

@@ -3,6 +3,7 @@
import { GetPickOrderLineInfo } from "@/app/api/pickOrder/actions";
import { QcItemWithChecks } from "@/app/api/qc";
import { PurchaseQcResult } from "@/app/api/po/actions";
import { StockInLine } from "@/app/api/po";
import {
Box,
Button,
@@ -187,7 +188,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
if (qcData.qcAccept) {
onOpenPutaway();
} else {
onClose();
onClose?.({} as object, "backdropClick");
}
},
[onOpenPutaway, qcItems],
@@ -281,7 +282,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
onSubmit={formProps.handleSubmit(onSubmitPutaway)}
>
<PutawayForm
itemDetail={itemDetail}
itemDetail={itemDetail as unknown as StockInLine}
warehouse={warehouse!}
disabled={false}
/>
@@ -341,7 +342,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
>
<QcFormVer2
qc={qc!}
itemDetail={itemDetail}
itemDetail={itemDetail as unknown as StockInLine}
disabled={false}
qcItems={qcItems}
setQcItems={setQcItems}


+ 3
- 2
src/components/PoDetail/EscalationTab.tsx Просмотреть файл

@@ -1,7 +1,7 @@
import StockInFormOld from "./StockInFormOld";
import EscalationLog from "./EscalationLog";
import EscalationComponent from "./EscalationComponent";
import React from "react";
import React, { useState } from "react";
import { PurchaseQcResult } from "@/app/api/po/actions";
import { StockInLine } from "@/app/api/po";

@@ -13,10 +13,11 @@ interface Props {
}

const EscalationTab:React.FC<Props> = ({itemDetail, disabled}) => {
const [isCollapsed, setIsCollapsed] = useState(false);
return <>
<StockInFormOld itemDetail={itemDetail} disabled={disabled}/>
<EscalationLog/>
<EscalationComponent/>
<EscalationComponent forSupervisor={false} isCollapsed={isCollapsed} setIsCollapsed={setIsCollapsed}/>
</>
};


+ 1
- 1
src/components/PoDetail/QCDatagrid.tsx Просмотреть файл

@@ -37,7 +37,7 @@ import {
GridApiCommunity,
GridSlotsComponentsProps,
} from "@mui/x-data-grid/internals";
import { dummyQCData } from "../Qc/dummyQcTemplate";
import { dummyQcData_A1 as dummyQCData } from "../Qc/dummyQcTemplate";
// T == CreatexxxInputs map of the form's fields
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc
// E == error


+ 7
- 7
src/components/PoDetail/QcFormOld.tsx Просмотреть файл

@@ -69,11 +69,11 @@ const QcFormOld: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
console.log(defaultValues);

//// validate form
const accQty = watch("acceptedQty");
const accQty = watch("acceptQty");
const validateForm = useCallback(() => {
console.log(accQty);
if (accQty > itemDetail.acceptedQty) {
setError("acceptedQty", {
setError("acceptQty", {
message: `${t("acceptedQty must not greater than")} ${
itemDetail.acceptedQty
}`,
@@ -81,13 +81,13 @@ const QcFormOld: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
});
}
if (accQty < 1) {
setError("acceptedQty", {
setError("acceptQty", {
message: t("minimal value is 1"),
type: "required",
});
}
if (isNaN(accQty)) {
setError("acceptedQty", {
setError("acceptQty", {
message: t("value must be a number"),
type: "required",
});
@@ -224,14 +224,14 @@ const QcFormOld: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
label={t("accepted Qty")}
fullWidth
// value={itemDetail.acceptedQty}
{...register("acceptedQty", {
{...register("acceptQty", {
required: "acceptedQty required!",
valueAsNumber: true,
max: itemDetail.acceptedQty,
})}
disabled={disabled}
error={Boolean(errors.acceptedQty)}
helperText={errors.acceptedQty?.message}
error={Boolean(errors.acceptQty)}
helperText={errors.acceptQty?.message}
/>
</Grid>
{/* <Grid item xs={12} lg={6}>


+ 1
- 1
src/components/PoDetail/QcStockInModal.tsx Просмотреть файл

@@ -20,7 +20,7 @@ import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-
import { StockInLineRow } from "./PoInputGrid";
import { useTranslation } from "react-i18next";
import StockInForm from "../StockIn/StockInForm";
import QcComponent from "./QcComponent";
import QcComponent from "../Qc/QcComponent";
import PutAwayForm from "./PutAwayForm";
import { GridRowModes, GridRowSelectionModel, useGridApiRef } from "@mui/x-data-grid";
import {msg, submitDialogWithWarning} from "../Swal/CustomAlerts";


+ 5
- 0
src/components/PoDetail/QrModal.tsx Просмотреть файл

@@ -10,6 +10,7 @@ import {
Typography,
} from "@mui/material";
import { useCallback, useEffect, useMemo, useState } from "react";
import { GridRowModesModel, GridRowSelectionModel } from "@mui/x-data-grid";
import ReactQrCodeScanner, {
ScannerConfig,
} from "../ReactQrCodeScanner/ReactQrCodeScanner";
@@ -107,6 +108,8 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => {
}, [scanner.values]);

const [itemDetail, setItemDetail] = useState<StockInLine>();
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>([]);
const [disabledSubmit, setDisabledSubmit] = useState(false);
const [unavailableText, setUnavailableText] = useState<string | undefined>(
undefined,
@@ -208,6 +211,8 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => {
itemDetail={itemDetail}
warehouse={warehouse}
disabled={false}
setRowModesModel={setRowModesModel}
setRowSelectionModel={setRowSelectionModel}
/>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button


+ 8
- 9
src/components/ProductionProcess/ProductionOutputFormPage.tsx Просмотреть файл

@@ -35,6 +35,13 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({
outputFromProcessUom: "",
defectQty: 0,
defectUom: "",
defect2Qty: 0,
defect2Uom: "",
defect3Qty: 0,
defect3Uom: "",
defectDescription: "",
defectDescription2: "",
defectDescription3: "",
scrapQty: 0,
scrapUom: "",
byproductName: "",
@@ -75,16 +82,8 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({

try {
await updateProductProcessLineQty({
...outputData,
productProcessLineId: lineDetail.id || 0,
byproductName: outputData.byproductName,
byproductQty: outputData.byproductQty,
byproductUom: outputData.byproductUom,
outputFromProcessQty: outputData.outputFromProcessQty,
outputFromProcessUom: outputData.outputFromProcessUom,
defectQty: outputData.defectQty,
defectUom: outputData.defectUom,
scrapQty: outputData.scrapQty,
scrapUom: outputData.scrapUom,
});

console.log("Output data submitted successfully");


+ 1
- 1
src/theme/EmotionCache.tsx Просмотреть файл

@@ -65,7 +65,7 @@ export default function NextAppDirEmotionCacheProvider(
inserted.forEach(({ name, isGlobal }) => {
const style = registry.cache.inserted[name];

if (typeof style !== "boolean") {
if (typeof style !== "boolean" && style != null) {
if (isGlobal) {
globals.push({ name, style });
} else {


+ 1
- 1
tsconfig.json Просмотреть файл

@@ -24,5 +24,5 @@
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"exclude": ["node_modules", "src/components/FinishedGoodSearch/newcreatitem copy.tsx", "src/components/PickOrderSearch/newcreatitem copy.tsx"]
}

Загрузка…
Отмена
Сохранить