diff --git a/check-translations.js b/check-translations.js
index fdd0cc9..c9b7138 100644
--- a/check-translations.js
+++ b/check-translations.js
@@ -11,7 +11,7 @@ function checkMissingTranslations(sourceFile, jsonFile) {
// 读取翻译 JSON 文件
const translations = JSON.parse(fs.readFileSync(jsonFile, 'utf-8'));
- // ✅ 只匹配 t('...') 和 t("...") 和 t(`...`),不包含模板变量
+ // 只匹配 t('...') 和 t("...") 和 t(`...`),不包含模板变量
const tRegex = /\bt\(["`']([^"`'${}]+)["`']\)/g;
const matches = [...sourceCode.matchAll(tRegex)];
@@ -86,7 +86,7 @@ if (args.length === 0) {
console.log(' node check-translations.js src/components/Jodetail/JodetailSearch.tsx src/i18n/zh/jo.json');
console.log(' node check-translations.js --dir src/components/Jodetail src/i18n/zh/jo.json');
console.log('\n注意:');
- console.log(' ✅ 只检查 t("key") 调用');
+ console.log(' 只检查 t("key") 调用');
console.log(' ❌ 忽略 alert(), console.log() 等普通字符串');
console.log(' ❌ 忽略模板字符串中的 ${} 变量部分');
process.exit(0);
@@ -103,7 +103,7 @@ if (args[0] === '--dir') {
const { results, totalMissing } = checkDirectory(directory, jsonFile);
if (Object.keys(results).length === 0) {
- console.log('✅ 太棒了!没有发现缺失的翻译键!');
+ console.log(' 太棒了!没有发现缺失的翻译键!');
} else {
console.log(`⚠️ 发现 ${Object.keys(results).length} 个文件有缺失的翻译键\n`);
@@ -153,7 +153,7 @@ if (args[0] === '--dir') {
});
console.log('─'.repeat(60));
} else {
- console.log('\n✅ 太棒了!所有使用的翻译键都已定义!');
+ console.log('\n 太棒了!所有使用的翻译键都已定义!');
}
if (result.unusedKeys.length > 0 && result.unusedKeys.length <= 20) {
diff --git a/src/app/(main)/production/page.tsx b/src/app/(main)/production/page.tsx
index f08ea76..24e9e30 100644
--- a/src/app/(main)/production/page.tsx
+++ b/src/app/(main)/production/page.tsx
@@ -1,4 +1,4 @@
-import ProductionProcess from "../../../components/ProductionProcess";
+import ProductionProcessPage from "../../../components/ProductionProcess/ProductionProcessPage";
import { getServerI18n } from "../../../i18n";
import Add from "@mui/icons-material/Add";
@@ -15,7 +15,6 @@ export const metadata: Metadata = {
const production: React.FC = async () => {
const { t } = await getServerI18n("claims");
- // preloadClaims();
return (
<>
@@ -26,22 +25,22 @@ const production: React.FC = async () => {
rowGap={2}
>
- {t("Production")}
+ {t("Production Process")}
- }
LinkComponent={Link}
href="/production/create"
>
- {t("Create Claim")}
-
+ {t("Create Process")}
+ */}
- {/* }> */}
-
- {/* */}
+ {/* Use new component */}
>
);
};
+
export default production;
diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts
index 353c799..f1cb0f3 100644
--- a/src/app/api/jo/actions.ts
+++ b/src/app/api/jo/actions.ts
@@ -129,13 +129,13 @@ export const recordSecondScanIssue = cache(async (
itemId: number,
data: {
qty: number; // verified qty (actual pick qty)
- missQty?: number; // ✅ 添加:miss qty
- badItemQty?: number; // ✅ 添加:bad item qty
+ missQty?: number; // 添加:miss qty
+ badItemQty?: number; // 添加:bad item qty
isMissing: boolean;
isBad: boolean;
reason: string;
createdBy: number;
- type?: string; // ✅ type 也应该是可选的
+ type?: string; // type 也应该是可选的
}
) => {
@@ -188,6 +188,136 @@ export interface ProductProcessWithLinesResponse {
date: string;
lines: ProductProcessLineResponse[];
}
+export interface UpdateProductProcessLineQtyRequest {
+ productProcessLineId: number;
+ outputFromProcessQty: number;
+ outputFromProcessUom: string;
+ defectQty: number;
+ defectUom: string;
+ scrapQty: number;
+ scrapUom: string;
+}
+export interface UpdateProductProcessLineQtyResponse {
+ id: number;
+ outputFromProcessQty: number;
+ outputFromProcessUom: string;
+ defectQty: number;
+ defectUom: string;
+ scrapQty: number;
+ scrapUom: string;
+ byproductName: string;
+ byproductQty: number;
+ byproductUom: string;
+}
+export interface AllProductProcessResponse {
+ id: number;
+ productProcessCode: string;
+ status: string;
+ startTime?: string;
+ endTime?: string;
+ date: string;
+ bomId?: number;
+}
+export interface AllJoborderProductProcessInfoResponse {
+ id: number;
+ productProcessCode: string;
+ status: string;
+ startTime?: string;
+ endTime?: string;
+ date: string;
+ bomId?: number;
+ itemName: string;
+ jobOrderId: number;
+ jobOrderCode: string;
+ productProcessLineCount: number;
+ FinishedProductProcessLineCount: number;
+ lines: ProductProcessInfoResponse[];
+}
+export interface ProductProcessInfoResponse {
+ id: number;
+ operatorId?: number;
+ operatorName?: string;
+ equipmentId?: number;
+ equipmentName?: string;
+ startTime?: string;
+ endTime?: string;
+ status: string;
+}
+export interface ProductProcessLineQrscanUpadteRequest {
+ lineId: number;
+ operatorId?: number;
+ equipmentId?: number;
+}
+export interface ProductProcessLineDetailResponse {
+ id: number,
+ productProcessId: number,
+ bomProcessId: number,
+ operatorId: number,
+ equipmentType: string,
+ operatorName: string,
+ handlerId: number,
+ seqNo: number,
+ name: string,
+ description: string,
+ equipment: string,
+ startTime: string,
+ endTime: string,
+ defectQty: number,
+ defectUom: string,
+ scrapQty: number,
+ scrapUom: string,
+ byproductId: number,
+ byproductName: string,
+ byproductQty: number,
+ byproductUom: string | undefined,
+}
+export const fetchProductProcessLineDetailByJoid = cache(async (joid: number) => {
+ return serverFetchJson(
+ `${BASE_API_URL}/product-process/demo/joid/${joid}`,
+ {
+ method: "GET",
+ }
+ );
+});
+
+// /product-process/Demo/ProcessLine/detail/{lineId}
+export const fetchProductProcessLineDetail = cache(async (lineId: number) => {
+ return serverFetchJson(
+ `${BASE_API_URL}/product-process/Demo/ProcessLine/detail/${lineId}`,
+ {
+ method: "GET",
+ }
+ );
+});
+export const updateProductProcessLineQrscan = cache(async (request: ProductProcessLineQrscanUpadteRequest) => {
+ return serverFetchJson(
+ `${BASE_API_URL}/product-process/Demo/update`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(request),
+ }
+ );
+});
+export const fetchAllJoborderProductProcessInfo = cache(async () => {
+ return serverFetchJson(
+ `${BASE_API_URL}/product-process/Demo/Process/all`,
+ {
+ method: "GET",
+ next: { tags: ["productProcess"] },
+ }
+ );
+});
+export const updateProductProcessLineQty = async (request: UpdateProductProcessLineQtyRequest) => {
+ return serverFetchJson(
+ `${BASE_API_URL}/product-process/lines/${request.productProcessLineId}/update/qty`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(request),
+ }
+ );
+};
export const startProductProcessLine = async (lineId: number, userId: number) => {
return serverFetchJson(
`${BASE_API_URL}/product-process/lines/${lineId}/start?userId=${userId}`,
diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts
index f8b0272..62bf102 100644
--- a/src/app/api/pickOrder/actions.ts
+++ b/src/app/api/pickOrder/actions.ts
@@ -95,12 +95,12 @@ export interface GetPickOrderInfoResponse {
export interface GetPickOrderInfo {
id: number;
code: string;
- consoCode: string | null; // ✅ 添加 consoCode 属性
- targetDate: string | number[]; // ✅ Support both formats
+ consoCode: string | null; // 添加 consoCode 属性
+ targetDate: string | number[]; // Support both formats
type: string;
status: string;
assignTo: number;
- groupName: string; // ✅ Add this field
+ groupName: string; // Add this field
pickOrderLines: GetPickOrderLineInfo[];
}
@@ -256,16 +256,16 @@ export interface stockReponse{
noLot: boolean;
}
export interface FGPickOrderResponse {
- // ✅ 新增:支持多个 pick orders
+ // 新增:支持多个 pick orders
doPickOrderId: number;
pickOrderIds?: number[];
- pickOrderCodes?: string[]; // ✅ 改为数组
+ pickOrderCodes?: string[]; // 改为数组
deliveryOrderIds?: number[];
- deliveryNos?: string[]; // ✅ 改为数组
+ deliveryNos?: string[]; // 改为数组
numberOfPickOrders?: number;
- lineCountsPerPickOrder?: number[];// ✅ 新增:pick order 数量
+ lineCountsPerPickOrder?: number[];// 新增:pick order 数量
- // ✅ 保留原有字段用于向后兼容(显示第一个 pick order)
+ // 保留原有字段用于向后兼容(显示第一个 pick order)
pickOrderId: number;
pickOrderCode: string;
pickOrderConsoCode: string;
@@ -332,17 +332,17 @@ export interface UpdateDoPickOrderHideStatusRequest {
}
export interface CompletedDoPickOrderResponse {
id: number;
- doPickOrderRecordId: number; // ✅ 新增
+ doPickOrderRecordId: number; // 新增
pickOrderId: number;
- pickOrderIds: number[]; // ✅ 新增:所有 pick order IDs
+ pickOrderIds: number[]; // 新增:所有 pick order IDs
pickOrderCode: string;
- pickOrderCodes: string; // ✅ 新增:所有 pick order codes (逗号分隔)
+ pickOrderCodes: string; // 新增:所有 pick order codes (逗号分隔)
pickOrderConsoCode: string;
pickOrderStatus: string;
deliveryOrderId: number;
- deliveryOrderIds: number[]; // ✅ 新增:所有 delivery order IDs
+ deliveryOrderIds: number[]; // 新增:所有 delivery order IDs
deliveryNo: string;
- deliveryNos: string; // ✅ 新增:所有 delivery order codes (逗号分隔)
+ deliveryNos: string; // 新增:所有 delivery order codes (逗号分隔)
deliveryDate: string;
shopId: number;
shopCode: string;
@@ -352,13 +352,13 @@ export interface CompletedDoPickOrderResponse {
shopPoNo: string;
numberOfCartons: number;
truckLanceCode: string;
- DepartureTime: string; // ✅ 新增
+ DepartureTime: string; // 新增
storeId: string;
completedDate: string;
fgPickOrders: FGPickOrderResponse[];
}
-// ✅ 新增:搜索参数接口
+// 新增:搜索参数接口
export interface CompletedDoPickOrderSearchParams {
pickOrderCode?: string;
shopName?: string;
@@ -491,7 +491,8 @@ export async function assignByLane(
userId: number,
storeId: string,
truckLanceCode: string,
- truckDepartureTime?: string
+ truckDepartureTime?: string,
+ requiredDate?: string
): Promise {
const response = await serverFetchJson(
`${BASE_API_URL}/doPickOrder/assign-by-lane`,
@@ -505,12 +506,13 @@ export async function assignByLane(
storeId,
truckLanceCode,
truckDepartureTime,
+ requiredDate,
}),
}
);
return response;
}
-// ✅ 新增:获取已完成的 DO Pick Orders API
+// 新增:获取已完成的 DO Pick Orders API
export const fetchCompletedDoPickOrders = async (
userId: number,
searchParams?: CompletedDoPickOrderSearchParams
@@ -919,7 +921,7 @@ export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): P
}
);
- console.log("✅ Fetched hierarchical lot details:", data);
+ console.log(" Fetched hierarchical lot details:", data);
return data;
} catch (error) {
console.error("❌ Error fetching hierarchical lot details:", error);
@@ -947,7 +949,7 @@ export const fetchLotDetailsByDoPickOrderRecordId = async (doPickOrderRecordId:
}
);
- console.log("✅ Fetched hierarchical lot details:", data);
+ console.log(" Fetched hierarchical lot details:", data);
return data;
} catch (error) {
console.error("❌ Error fetching lot details:", error);
@@ -962,7 +964,7 @@ export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Pro
try {
console.log("🔍 Fetching all pick order line lot details for userId:", userId);
- // ✅ Use the non-auto-assign endpoint
+ // Use the non-auto-assign endpoint
const data = await serverFetchJson(
`${BASE_API_URL}/pickOrder/all-lots-with-details-no-auto-assign/${userId}`,
{
@@ -971,7 +973,7 @@ export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Pro
}
);
- console.log("✅ Fetched lot details:", data);
+ console.log(" Fetched lot details:", data);
return data;
} catch (error) {
console.error("❌ Error fetching lot details:", error);
@@ -987,7 +989,7 @@ export const fetchAllPickOrderDetails = cache(async (userId?: number) => {
};
}
- // ✅ Use the correct endpoint with userId in the path
+ // Use the correct endpoint with userId in the path
const url = `${BASE_API_URL}/pickOrder/detail-optimized/${userId}`;
return serverFetchJson(
diff --git a/src/components/DoDetail/DoDetail.tsx b/src/components/DoDetail/DoDetail.tsx
index 9f3bd0b..49d8940 100644
--- a/src/components/DoDetail/DoDetail.tsx
+++ b/src/components/DoDetail/DoDetail.tsx
@@ -13,7 +13,7 @@ import { releaseDo, assignPickOrderByStore, releaseAssignedPickOrderByStore } fr
import DoInfoCard from "./DoInfoCard";
import DoLineTable from "./DoLineTable";
import { useSession } from "next-auth/react";
-import { SessionWithTokens } from "@/config/authConfig"; // ✅ Import the correct session type
+import { SessionWithTokens } from "@/config/authConfig"; // Import the correct session type
type Props = {
id?: number;
@@ -30,9 +30,9 @@ const DoDetail: React.FC = ({
const [serverError, setServerError] = useState("");
const [successMessage, setSuccessMessage] = useState("");
const [isAssigning, setIsAssigning] = useState(false);
- const { data: session } = useSession() as { data: SessionWithTokens | null }; // ✅ Use correct session type
+ const { data: session } = useSession() as { data: SessionWithTokens | null }; // Use correct session type
- const currentUserId = session?.id ? parseInt(session.id) : undefined; // ✅ Get user ID from session.id
+ const currentUserId = session?.id ? parseInt(session.id) : undefined; // Get user ID from session.id
console.log("🔍 DoSearch - session:", session);
console.log("🔍 DoSearch - currentUserId:", currentUserId);
const formProps = useForm({
@@ -50,7 +50,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
setSuccessMessage("")
if (id) {
- // ✅ Get current user ID from session
+ // Get current user ID from session
const currentUserId = session?.id ? parseInt(session.id) : undefined;
if (!currentUserId) {
@@ -60,7 +60,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
const response = await releaseDo({
id: id,
- userId: currentUserId // ✅ Pass user ID from session
+ userId: currentUserId // Pass user ID from session
})
if (response) {
@@ -74,16 +74,16 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
} finally {
setIsUploading(false)
}
- }, [id, formProps, t, setIsUploading, session]) // ✅ Add session to dependencies
+ }, [id, formProps, t, setIsUploading, session]) // Add session to dependencies
- // ✅ UPDATE STORE-BASED ASSIGNMENT HANDLERS
+ // UPDATE STORE-BASED ASSIGNMENT HANDLERS
const handleAssignByStore = useCallback(async (storeId: string) => {
try {
setIsAssigning(true)
setServerError("")
setSuccessMessage("")
- // ✅ Get current user ID from session
+ // Get current user ID from session
const currentUserId = session?.id ? parseInt(session.id) : undefined;
if (!currentUserId) {
@@ -107,7 +107,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
} finally {
setIsAssigning(false)
}
- }, [t, session]) // ✅ Add session to dependencies
+ }, [t, session]) // Add session to dependencies
const handleReleaseByStore = useCallback(async (storeId: string) => {
try {
@@ -115,7 +115,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
setServerError("")
setSuccessMessage("")
- // ✅ Get current user ID from session
+ // Get current user ID from session
const currentUserId = session?.id ? parseInt(session.id) : undefined;
if (!currentUserId) {
@@ -139,7 +139,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
} finally {
setIsAssigning(false)
}
- }, [t, session]) // ✅ Add session to dependencies
+ }, [t, session]) // Add session to dependencies
const onSubmit = useCallback>(async (data, event) => {
console.log(data)
@@ -182,7 +182,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
)}
- {/* ✅ ADD STORE-BASED ASSIGNMENT BUTTONS */}
+ {/* ADD STORE-BASED ASSIGNMENT BUTTONS */}
{
formProps.watch("status")?.toLowerCase() === "released" && (
diff --git a/src/components/FinishedGoodSearch/CombinedLotTable.tsx b/src/components/FinishedGoodSearch/CombinedLotTable.tsx
index f594d94..459c935 100644
--- a/src/components/FinishedGoodSearch/CombinedLotTable.tsx
+++ b/src/components/FinishedGoodSearch/CombinedLotTable.tsx
@@ -34,7 +34,7 @@ interface CombinedLotTableProps {
onPageSizeChange: (event: React.ChangeEvent) => void;
}
-// ✅ Simple helper function to check if item is completed
+// Simple helper function to check if item is completed
const isItemCompleted = (lot: any) => {
const actualPickQty = Number(lot.actualPickQty) || 0;
const requiredQty = Number(lot.requiredQty) || 0;
@@ -60,7 +60,7 @@ const CombinedLotTable: React.FC = ({
}) => {
const { t } = useTranslation("pickOrder");
- // ✅ Paginated data
+ // Paginated data
const paginatedLotData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize;
const endIndex = startIndex + paginationController.pageSize;
@@ -113,7 +113,7 @@ const CombinedLotTable: React.FC = ({
const isCompleted = isItemCompleted(lot);
const isRejected = isItemRejected(lot);
- // ✅ Green text color for completed items
+ // Green text color for completed items
const textColor = isCompleted ? 'success.main' : isRejected ? 'error.main' : 'inherit';
return (
diff --git a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx
index 3ee7c1a..be5964b 100644
--- a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx
+++ b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx
@@ -47,7 +47,8 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) =>
const handleAssignByLane = useCallback(async (
storeId: string,
truckDepartureTime: string,
- truckLanceCode: string
+ truckLanceCode: string,
+ requiredDate: string
) => {
if (!currentUserId) {
console.error("Missing user id in session");
@@ -56,10 +57,10 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) =>
setIsAssigning(true);
try {
- const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
+ const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, requiredDate);
if (res.code === "SUCCESS") {
- console.log("✅ Successfully assigned pick order from lane", truckLanceCode);
+ console.log(" Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态
onPickOrderAssigned?.();
@@ -231,7 +232,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) =>
variant="outlined"
size="medium"
disabled={item.lane.unassigned === 0 || isAssigning}
- onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode)}
+ onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{
flex: 1,
fontSize: '1.1rem',
@@ -344,7 +345,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) =>
variant="outlined"
size="medium"
disabled={item.lane.unassigned === 0 || isAssigning}
- onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode)}
+ onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{
flex: 1,
fontSize: '1.1rem',
diff --git a/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx b/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx
index 3d88dce..1afad92 100644
--- a/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx
+++ b/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx
@@ -27,22 +27,22 @@ const FGPickOrderInfoCard: React.FC = ({ fgOrder, doPickOrderDetail }) =>
diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx
index 1961041..4669828 100644
--- a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx
+++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx
@@ -56,22 +56,31 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) =>
loadSummaries();
}, [loadSummaries]);
- const handleAssignByLane = useCallback(async (
- storeId: string,
- truckDepartureTime: string,
- truckLanceCode: string
- ) => {
- if (!currentUserId) {
- console.error("Missing user id in session");
- return;
- }
-
- setIsAssigning(true);
- try {
- const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
+ const handleAssignByLane = useCallback(async (
+ storeId: string,
+ truckDepartureTime: string,
+ truckLanceCode: string,
+ requiredDate: string
+
+ ) => {
+ if (!currentUserId) {
+ console.error("Missing user id in session");
+ return;
+ }
+ let dateParam: string | undefined;
+ if (requiredDate === "today") {
+ dateParam = dayjs().format('YYYY-MM-DD');
+ } else if (requiredDate === "tomorrow") {
+ dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD');
+ } else if (requiredDate === "dayAfterTomorrow") {
+ dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD');
+ }
+ setIsAssigning(true);
+ try {
+ const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam);
if (res.code === "SUCCESS") {
- console.log("✅ Successfully assigned pick order from lane", truckLanceCode);
+ console.log(" Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态
onPickOrderAssigned?.();
@@ -236,7 +245,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) =>
variant="outlined"
size="medium"
disabled={item.lane.unassigned === 0 || isAssigning}
- onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode)}
+ onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{
flex: 1,
fontSize: '1.1rem',
@@ -336,7 +345,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) =>
variant="outlined"
size="medium"
disabled={item.lane.unassigned === 0 || isAssigning}
- onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode)}
+ onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{
flex: 1,
fontSize: '1.1rem',
diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
index 8c3ec12..ff54a88 100644
--- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
+++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
@@ -247,7 +247,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => {
const handleCompletionStatusChange = (event: CustomEvent) => {
const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail;
- // ✅ 修复:根据标签页和事件来源决定是否更新打印按钮状态
+ // 修复:根据标签页和事件来源决定是否更新打印按钮状态
if (eventTabIndex === undefined || eventTabIndex === tabIndex) {
setPrintButtonsEnabled(allLotsCompleted);
console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted);
@@ -259,9 +259,9 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => {
return () => {
window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
};
- }, [tabIndex]); // ✅ 添加 tabIndex 依赖
+ }, [tabIndex]); // 添加 tabIndex 依赖
- // ✅ 新增:处理标签页切换时的打印按钮状态重置
+ // 新增:处理标签页切换时的打印按钮状态重置
useEffect(() => {
// 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态
if (tabIndex === 2) {
@@ -286,7 +286,7 @@ const handleAssignByLane = useCallback(async (
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
if (res.code === "SUCCESS") {
- console.log("✅ Successfully assigned pick order from lane", truckLanceCode);
+ console.log(" Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态
} else if (res.code === "USER_BUSY") {
@@ -322,7 +322,7 @@ const handleAssignByLane = useCallback(async (
setIsAssigning(false);
}
}, [currentUserId, t, loadSummaries]);
- // ✅ Manual assignment handler - uses the action function
+ // Manual assignment handler - uses the action function
*/
const handleTabChange = useCallback>(
(_e, newValue) => {
@@ -607,7 +607,7 @@ const handleAssignByLane = useCallback(async (
- {/* Tabs section - ✅ Move the click handler here */}
+ {/* Tabs section - Move the click handler here */}
diff --git a/src/components/FinishedGoodSearch/GoodPickExecution.tsx b/src/components/FinishedGoodSearch/GoodPickExecution.tsx
index 4f2ba06..ca39390 100644
--- a/src/components/FinishedGoodSearch/GoodPickExecution.tsx
+++ b/src/components/FinishedGoodSearch/GoodPickExecution.tsx
@@ -26,7 +26,7 @@ import {
updateStockOutLineStatus,
createStockOutLine,
recordPickExecutionIssue,
- fetchFGPickOrdersByUserId, // ✅ Add this import
+ fetchFGPickOrdersByUserId, // Add this import
FGPickOrderResponse,
autoAssignAndReleasePickOrder,
AutoAssignReleaseResponse,
@@ -59,13 +59,13 @@ interface Props {
onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void;
}
-// ✅ QR Code Modal Component (from LotTable)
+// QR Code Modal Component (from LotTable)
const QrCodeModal: React.FC<{
open: boolean;
onClose: () => void;
lot: any | null;
onQrCodeSubmit: (lotNo: string) => void;
- combinedLotData: any[]; // ✅ Add this prop
+ combinedLotData: any[]; // Add this prop
}> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => {
const { t } = useTranslation("pickOrder");
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
@@ -105,7 +105,7 @@ const QrCodeModal: React.FC<{
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
if (stockInLineInfo.lotNo === lot.lotNo) {
- console.log(`✅ QR Code verified for lot: ${lot.lotNo}`);
+ console.log(` QR Code verified for lot: ${lot.lotNo}`);
setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo);
onClose();
@@ -297,7 +297,7 @@ const QrCodeModal: React.FC<{
{qrScanSuccess && (
- ✅ {t("Verified successfully!")}
+ {t("Verified successfully!")}
)}
@@ -348,11 +348,11 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const formProps = useForm();
const errors = formProps.formState.errors;
- // ✅ Add QR modal states
+ // Add QR modal states
const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState(null);
- // ✅ Add GoodPickExecutionForm states
+ // Add GoodPickExecutionForm states
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState(null);
const [fgPickOrders, setFgPickOrders] = useState([]);
@@ -389,14 +389,14 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [currentUserId, selectedPickOrderId]);
- // ✅ 简化:移除复杂的 useEffect 依赖
+ // 简化:移除复杂的 useEffect 依赖
useEffect(() => {
if (currentUserId) {
fetchFgPickOrdersData();
}
}, [currentUserId, fetchFgPickOrdersData]);
- // ✅ Handle QR code button click
+ // Handle QR code button click
const handleQrCodeClick = (pickOrderId: number) => {
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
// TODO: Implement QR code functionality
@@ -424,22 +424,22 @@ const fetchFgPickOrdersData = useCallback(async () => {
return;
}
- // ✅ Use the non-auto-assign endpoint - this only fetches existing data
+ // Use the non-auto-assign endpoint - this only fetches existing data
const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse);
- console.log("✅ All combined lot details:", allLotDetails);
+ console.log(" All combined lot details:", allLotDetails);
setCombinedLotData(allLotDetails);
setOriginalCombinedData(allLotDetails);
- // ✅ 计算完成状态并发送事件
+ // 计算完成状态并发送事件
const allCompleted = allLotDetails.length > 0 && allLotDetails.every(lot =>
lot.processingStatus === 'completed'
);
- // ✅ 发送完成状态事件,包含标签页信息
+ // 发送完成状态事件,包含标签页信息
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: {
allLotsCompleted: allCompleted,
- tabIndex: 0 // ✅ 明确指定这是来自标签页 0 的事件
+ tabIndex: 0 // 明确指定这是来自标签页 0 的事件
}
}));
@@ -448,7 +448,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
setCombinedLotData([]);
setOriginalCombinedData([]);
- // ✅ 如果加载失败,禁用打印按钮
+ // 如果加载失败,禁用打印按钮
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: {
allLotsCompleted: false,
@@ -460,18 +460,18 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [currentUserId, combinedLotData]);
- // ✅ Only fetch existing data when session is ready, no auto-assignment
+ // Only fetch existing data when session is ready, no auto-assignment
useEffect(() => {
if (session && currentUserId && !initializationRef.current) {
- console.log("✅ Session loaded, initializing pick order...");
+ console.log(" Session loaded, initializing pick order...");
initializationRef.current = true;
- // ✅ Only fetch existing data, no auto-assignment
+ // Only fetch existing data, no auto-assignment
fetchAllCombinedLotData();
}
}, [session, currentUserId, fetchAllCombinedLotData]);
- // ✅ Add event listener for manual assignment
+ // Add event listener for manual assignment
useEffect(() => {
const handlePickOrderAssigned = () => {
console.log("🔄 Pick order assigned event received, refreshing data...");
@@ -485,12 +485,12 @@ const fetchFgPickOrdersData = useCallback(async () => {
};
}, [fetchAllCombinedLotData]);
- // ✅ Handle QR code submission for matched lot (external scanning)
- // ✅ Handle QR code submission for matched lot (external scanning)
+ // Handle QR code submission for matched lot (external scanning)
+ // Handle QR code submission for matched lot (external scanning)
const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
- console.log(`✅ Processing QR Code for lot: ${lotNo}`);
+ console.log(` Processing QR Code for lot: ${lotNo}`);
- // ✅ Use current data without refreshing to avoid infinite loop
+ // Use current data without refreshing to avoid infinite loop
const currentLotData = combinedLotData;
console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo));
@@ -506,7 +506,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
return;
}
- console.log(`✅ Found ${matchingLots.length} matching lots:`, matchingLots);
+ console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
setQrScanError(false);
try {
@@ -518,7 +518,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`);
if (matchingLot.stockOutLineId) {
- console.log(`✅ Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
+ console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
existsCount++;
} else {
const stockOutLineData: CreateStockOutLine = {
@@ -533,10 +533,10 @@ const fetchFgPickOrdersData = useCallback(async () => {
console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, result);
if (result && result.code === "EXISTS") {
- console.log(`✅ Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
+ console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
existsCount++;
} else if (result && result.code === "SUCCESS") {
- console.log(`✅ Stock out line created successfully for line ${matchingLot.pickOrderLineId}`);
+ console.log(` Stock out line created successfully for line ${matchingLot.pickOrderLineId}`);
successCount++;
} else {
console.error(`❌ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, result);
@@ -545,16 +545,16 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}
- // ✅ Always refresh data after processing (success or failure)
+ // Always refresh data after processing (success or failure)
console.log("🔄 Refreshing data after QR code processing...");
await fetchAllCombinedLotData();
if (successCount > 0 || existsCount > 0) {
- console.log(`✅ QR Code processing completed: ${successCount} created, ${existsCount} already existed`);
+ console.log(` QR Code processing completed: ${successCount} created, ${existsCount} already existed`);
setQrScanSuccess(true);
setQrScanInput(''); // Clear input after successful processing
- // ✅ Clear success state after a delay
+ // Clear success state after a delay
setTimeout(() => {
setQrScanSuccess(false);
}, 2000);
@@ -563,7 +563,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
setQrScanError(true);
setQrScanSuccess(false);
- // ✅ Clear error state after a delay
+ // Clear error state after a delay
setTimeout(() => {
setQrScanError(false);
}, 3000);
@@ -573,10 +573,10 @@ const fetchFgPickOrdersData = useCallback(async () => {
setQrScanError(true);
setQrScanSuccess(false);
- // ✅ Still refresh data even on error
+ // Still refresh data even on error
await fetchAllCombinedLotData();
- // ✅ Clear error state after a delay
+ // Clear error state after a delay
setTimeout(() => {
setQrScanError(false);
}, 3000);
@@ -589,17 +589,17 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [qrScanInput, handleQrCodeSubmit]);
- // ✅ Handle QR code submission from modal (internal scanning)
+ // Handle QR code submission from modal (internal scanning)
const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
- console.log(`✅ QR Code verified for lot: ${lotNo}`);
+ console.log(` QR Code verified for lot: ${lotNo}`);
const requiredQty = selectedLotForQr.requiredQty;
const lotId = selectedLotForQr.lotId;
// Create stock out line
const stockOutLineData: CreateStockOutLine = {
- consoCode: selectedLotForQr.pickOrderConsoCode, // ✅ Use pickOrderConsoCode instead of pickOrderCode
+ consoCode: selectedLotForQr.pickOrderConsoCode, // Use pickOrderConsoCode instead of pickOrderCode
pickOrderLineId: selectedLotForQr.pickOrderLineId,
inventoryLotLineId: selectedLotForQr.lotId,
qty: 0.0
@@ -620,7 +620,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
...prev,
[lotKey]: requiredQty
}));
- console.log(`✅ Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
+ console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
}, 500);
// Refresh data
@@ -631,7 +631,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [selectedLotForQr, fetchAllCombinedLotData]);
- // ✅ Outside QR scanning - process QR codes from outside the page automatically
+ // Outside QR scanning - process QR codes from outside the page automatically
useEffect(() => {
if (qrValues.length > 0 && combinedLotData.length > 0) {
const latestQr = qrValues[qrValues.length - 1];
@@ -707,7 +707,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) {
console.log("Found completed pick orders, auto-assigning next...");
- // ✅ 移除前端的自动分配逻辑,因为后端已经处理了
+ // 移除前端的自动分配逻辑,因为后端已经处理了
// await handleAutoAssignAndRelease(); // 删除这个函数
}
} catch (error) {
@@ -715,7 +715,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [currentUserId]);
- // ✅ Handle submit pick quantity
+ // Handle submit pick quantity
const handleSubmitPickQty = useCallback(async (lot: any) => {
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
const newQty = pickQtyData[lotKey] || 0;
@@ -759,14 +759,14 @@ const fetchFgPickOrdersData = useCallback(async () => {
});
}
- // ✅ FIXED: Use the proper API function instead of direct fetch
+ // FIXED: Use the proper API function instead of direct fetch
if (newStatus === 'completed' && lot.pickOrderConsoCode) {
- console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
+ console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try {
- // ✅ Use the imported API function instead of direct fetch
+ // Use the imported API function instead of direct fetch
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
- console.log(`✅ Pick order completion check result:`, completionResponse);
+ console.log(` Pick order completion check result:`, completionResponse);
if (completionResponse.code === "SUCCESS") {
console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
@@ -792,7 +792,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]);
- // ✅ Handle reject lot
+ // Handle reject lot
const handleRejectLot = useCallback(async (lot: any) => {
if (!lot.stockOutLineId) {
console.error("No stock out line found for this lot");
@@ -818,7 +818,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [fetchAllCombinedLotData, checkAndAutoAssignNext]);
- // ✅ Handle pick execution form
+ // Handle pick execution form
const handlePickExecutionForm = useCallback((lot: any) => {
console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot);
@@ -847,7 +847,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
console.log("Pick execution issue recorded:", result);
if (result && result.code === "SUCCESS") {
- console.log("✅ Pick execution issue recorded successfully");
+ console.log(" Pick execution issue recorded successfully");
} else {
console.error("❌ Failed to record pick execution issue:", result);
}
@@ -861,7 +861,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
}
}, [fetchAllCombinedLotData]);
- // ✅ Calculate remaining required quantity
+ // Calculate remaining required quantity
const calculateRemainingRequiredQty = useCallback((lot: any) => {
const requiredQty = lot.requiredQty || 0;
const stockOutLineQty = lot.stockOutLineQty || 0;
@@ -942,7 +942,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
// Pagination data with sorting by routerIndex
const paginatedData = useMemo(() => {
- // ✅ Sort by routerIndex first, then by other criteria
+ // Sort by routerIndex first, then by other criteria
const sortedData = [...combinedLotData].sort((a, b) => {
const aIndex = a.routerIndex || 0;
const bIndex = b.routerIndex || 0;
@@ -970,14 +970,14 @@ const fetchFgPickOrdersData = useCallback(async () => {
return (
- {/* ✅ 修复:改进条件渲染逻辑 */}
+ {/* 修复:改进条件渲染逻辑 */}
{combinedDataLoading || fgPickOrdersLoading ? (
- // ✅ 数据加载中,显示加载指示器
+ // 数据加载中,显示加载指示器
) : fgPickOrders.length === 0 ? (
- // ✅ 没有活动订单,显示楼层选择面板
+ // 没有活动订单,显示楼层选择面板
{
if (currentUserId) {
@@ -987,7 +987,7 @@ return (
}}
/>
) : (
- // ✅ 有活动订单,显示 FG 订单信息
+ // 有活动订单,显示 FG 订单信息
{fgPickOrders.map((fgOrder) => (
diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
index ab9f480..8a9a76b 100644
--- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
+++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
@@ -53,7 +53,7 @@ interface PickExecutionFormProps {
selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null;
pickOrderId?: number;
pickOrderCreateDate: any;
- // ✅ Remove these props since we're not handling normal cases
+ // Remove these props since we're not handling normal cases
// onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise;
// selectedRowId?: number | null;
}
@@ -75,7 +75,7 @@ const PickExecutionForm: React.FC = ({
selectedPickOrderLine,
pickOrderId,
pickOrderCreateDate,
- // ✅ Remove these props
+ // Remove these props
// onNormalPickSubmit,
// selectedRowId,
}) => {
@@ -86,11 +86,11 @@ const PickExecutionForm: React.FC = ({
const [handlers, setHandlers] = useState>([]);
// 计算剩余可用数量
const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => {
- // ✅ 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty
+ // 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty
return lot.availableQty || 0;
}, []);
const calculateRequiredQty = useCallback((lot: LotPickData) => {
- // ✅ Use the original required quantity, not subtracting actualPickQty
+ // Use the original required quantity, not subtracting actualPickQty
// The actualPickQty in the form should be independent of the database value
return lot.requiredQty || 0;
}, []);
@@ -175,7 +175,7 @@ const PickExecutionForm: React.FC = ({
}
}, [errors]);
- // ✅ Update form validation to require either missQty > 0 OR badItemQty > 0
+ // Update form validation to require either missQty > 0 OR badItemQty > 0
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
const req = selectedLot?.requiredQty || 0;
@@ -200,10 +200,10 @@ const PickExecutionForm: React.FC = ({
};
const handleSubmit = async () => {
- // ✅ 先验证表单
+ // First validate the form
if (!validateForm()) {
console.error('Form validation failed:', errors);
- return; // ✅ 阻止提交,显示验证错误
+ return; // Prevent submission, show validation errors
}
if (!formData.pickOrderId) {
@@ -214,10 +214,10 @@ const PickExecutionForm: React.FC = ({
setLoading(true);
try {
await onSubmit(formData as PickExecutionIssueData);
- // ✅ 成功时会自动关闭(由 onClose 处理)
+ // Automatically closed when successful (handled by onClose)
} catch (error: any) {
console.error('Error submitting pick execution issue:', error);
- // ✅ 显示错误消息(可以通过 props 或 state 传递错误消息到父组件)
+ // Show error message (can be passed to parent component via props or state)
// 或者在这里显示 toast/alert
alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : ''));
} finally {
@@ -241,11 +241,11 @@ const PickExecutionForm: React.FC = ({
return (
+ );
+};
+
+export default ProductionProcessDetail;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx
new file mode 100644
index 0000000..8180892
--- /dev/null
+++ b/src/components/ProductionProcess/ProductionProcessList.tsx
@@ -0,0 +1,185 @@
+"use client";
+import React, { useCallback, useEffect, useState } from "react";
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ CardActions,
+ Stack,
+ Typography,
+ Chip,
+ CircularProgress,
+ TablePagination,
+ Grid,
+} from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { useSession } from "next-auth/react";
+import { SessionWithTokens } from "@/config/authConfig";
+import dayjs from "dayjs";
+import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
+import {
+ fetchAllJoborderProductProcessInfo,
+ AllJoborderProductProcessInfoResponse,
+} from "@/app/api/jo/actions";
+
+interface ProductProcessListProps {
+ onSelectProcess: (processId: number) => void;
+}
+
+const PER_PAGE = 6;
+
+const ProductProcessList: React.FC = ({ onSelectProcess }) => {
+ const { t } = useTranslation();
+ const { data: session } = useSession() as { data: SessionWithTokens | null };
+
+ const [loading, setLoading] = useState(false);
+ const [processes, setProcesses] = useState([]);
+ const [page, setPage] = useState(0);
+
+ const fetchProcesses = useCallback(async () => {
+ setLoading(true);
+ try {
+ const data = await fetchAllJoborderProductProcessInfo();
+ setProcesses(data || []);
+ setPage(0);
+ } catch (e) {
+ console.error(e);
+ setProcesses([]);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ fetchProcesses();
+ }, [fetchProcesses]);
+
+ const startIdx = page * PER_PAGE;
+ const paged = processes.slice(startIdx, startIdx + PER_PAGE);
+
+ return (
+
+ {loading ? (
+
+
+
+ ) : (
+
+
+ {t("Total processes")}: {processes.length}
+
+
+
+ {paged.map((process) => {
+ const status = String(process.status || "");
+ const statusLower = status.toLowerCase();
+ const statusColor =
+ statusLower === "completed"
+ ? "success"
+ : statusLower === "in_progress" || statusLower === "processing"
+ ? "primary"
+ : "default";
+
+ const finishedCount =
+ (process as any).finishedProductProcessLineCount ??
+ (process as any).FinishedProductProcessLineCount ??
+ 0;
+
+ const totalCount = process.productProcessLineCount ?? process.lines?.length ?? 0;
+ const linesWithStatus = (process.lines || []).filter(
+ (l) => String(l.status ?? "").trim() !== ""
+ );
+
+ const dateDisplay = process.date
+ ? dayjs(process.date as any).format(OUTPUT_DATE_FORMAT)
+ : "-";
+ const jobOrderCode =
+ (process as any).jobOrderCode ??
+ (process.jobOrderId ? `JO-${process.jobOrderId}` : "N/A");
+ const inProgressLines = (process.lines || [])
+ .filter(l => String(l.status ?? "").trim() !== "")
+ .filter(l => String(l.status).toLowerCase() === "in_progress");
+
+ return (
+
+
+
+
+
+
+ {process.productProcessCode}
+
+
+ {t("Job Order")}: {jobOrderCode}
+
+
+
+
+
+
+ {statusLower !== "pending" && linesWithStatus.length > 0 && (
+
+
+ {t("Finished lines")}: {finishedCount} / {totalCount}
+
+
+ {inProgressLines.length > 0 && (
+
+ {inProgressLines.map(line => (
+
+ {t("Operator")}: {line.operatorName || "-"}
+ {t("Equipment")}: {line.equipmentName || "-"}
+
+ ))}
+
+ )}
+
+ )}
+
+
+
+ onSelectProcess(process.id)}>
+ {t("View Details")}
+
+
+
+ {t("Lines")}: {totalCount}
+
+
+
+
+ );
+ })}
+
+
+ {processes.length > 0 && (
+ setPage(p)}
+ rowsPerPageOptions={[PER_PAGE]}
+ />
+ )}
+
+ )}
+
+ );
+};
+
+export default ProductProcessList;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/ProductionProcessPage.tsx b/src/components/ProductionProcess/ProductionProcessPage.tsx
new file mode 100644
index 0000000..4343ac4
--- /dev/null
+++ b/src/components/ProductionProcess/ProductionProcessPage.tsx
@@ -0,0 +1,67 @@
+"use client";
+import React, { useState, useEffect, useCallback } from "react";
+import { useSession } from "next-auth/react";
+import { SessionWithTokens } from "@/config/authConfig";
+import ProductionProcessList from "@/components/ProductionProcess/ProductionProcessList";
+import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail";
+import {
+ fetchProductProcesses,
+ fetchProductProcessLines,
+ ProductProcessLineResponse
+} from "@/app/api/jo/actions";
+
+const ProductionProcessPage = () => {
+ const [selectedProcessId, setSelectedProcessId] = useState(null);
+ const { data: session } = useSession() as { data: SessionWithTokens | null };
+ const currentUserId = session?.id ? parseInt(session.id) : undefined;
+
+ const checkAndRedirectToDetail = useCallback(async () => {
+ if (!currentUserId) return;
+
+ try {
+ // 获取所有 processes
+ const processes = await fetchProductProcesses();
+
+ // 获取所有 lines 并检查是否有匹配的
+ for (const process of processes.content || []) {
+ const lines = await fetchProductProcessLines(process.id);
+ const pendingLine = lines.find((line: ProductProcessLineResponse) =>
+ line.handlerId === currentUserId &&
+ !line.endTime &&
+ line.startTime
+ );
+
+ if (pendingLine) {
+ setSelectedProcessId(process.id);
+ break;
+ }
+ }
+ } catch (error) {
+ console.error("Error checking pending lines:", error);
+ }
+ }, [currentUserId]);
+
+ useEffect(() => {
+ if (currentUserId && !selectedProcessId) {
+ // 检查是否有当前用户的 pending line
+ checkAndRedirectToDetail();
+ }
+ }, [currentUserId, selectedProcessId, checkAndRedirectToDetail]);
+
+ if (selectedProcessId !== null) {
+ return (
+ setSelectedProcessId(null)}
+ />
+ );
+ }
+
+ return (
+ setSelectedProcessId(id)}
+ />
+ );
+};
+
+export default ProductionProcessPage;
\ No newline at end of file