From dc7adc9d9b19e596c66d00119ae5975580abfcb3 Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Thu, 18 Dec 2025 11:05:08 +0800 Subject: [PATCH] making the autogen --- src/app/api/scheduling/actions.ts | 26 +++ src/app/api/scheduling/index.ts | 5 + .../DetailedScheduleSearchView.tsx | 48 ++--- .../DetailedScheduleDetailView.tsx | 121 +++++++---- .../DetailedScheduleDetailWrapper.tsx | 31 +-- .../ViewByFGDetails.tsx | 200 +++++------------- 6 files changed, 205 insertions(+), 226 deletions(-) diff --git a/src/app/api/scheduling/actions.ts b/src/app/api/scheduling/actions.ts index 729c998..0cc4b05 100644 --- a/src/app/api/scheduling/actions.ts +++ b/src/app/api/scheduling/actions.ts @@ -39,6 +39,10 @@ export interface ReleaseProdScheduleInputs { demandQty: number; } +export interface ReleaseProdScheduleReq { + id: number; +} + export interface ReleaseProdScheduleResponse { id: number; code: string; @@ -48,6 +52,12 @@ export interface ReleaseProdScheduleResponse { message: string; } +export interface ReleaseProdScheduleRsp { + id: number; + code: string; + message: string; +} + export interface SaveProdScheduleResponse { id: number; code: string; @@ -151,6 +161,22 @@ export const releaseProdScheduleLine = cache(async (data: ReleaseProdScheduleInp return response; }) +export const releaseProdSchedule = cache(async (data: ReleaseProdScheduleReq) => { + const response = serverFetchJson( + `${BASE_API_URL}/productionSchedule/detail/detailed/release`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + } + ); + + //revalidateTag("detailedProdSchedules"); + //revalidateTag("prodSchedule"); + + return response; +}) + export const saveProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => { const response = serverFetchJson( `${BASE_API_URL}/productionSchedule/detail/detailed/save`, diff --git a/src/app/api/scheduling/index.ts b/src/app/api/scheduling/index.ts index 482ddf5..5ffa663 100644 --- a/src/app/api/scheduling/index.ts +++ b/src/app/api/scheduling/index.ts @@ -100,6 +100,11 @@ export interface DetailedProdScheduleLineResult { priority: number; approved: boolean; proportion: number; + lastMonthAvgSales: number; + avgQtyLastMonth: number; // Average usage last month + stockQty: number; // Warehouse stock quantity + daysLeft: number; // Days remaining before stockout + needNoOfJobOrder: number; } export interface DetailedProdScheduleLineBomMaterialResult { diff --git a/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx b/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx index 2848949..76d80ce 100644 --- a/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx +++ b/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx @@ -77,17 +77,17 @@ const DSOverview: React.FC = ({ type, defaultInputs }) => { // type: "dateRange", // }, { label: t("Production Date"), paramName: "scheduleAt", type: "date" }, - { - label: t("Product Count"), - paramName: "totalEstProdCount", - type: "text", - }, - { - label: t("Type"), - paramName: "types", - type: "autocomplete", - options: typeOptions, - }, + //{ + // label: t("Product Count"), + // paramName: "totalEstProdCount", + // type: "text", + //}, + //{ + // label: t("Type"), + // paramName: "types", + // type: "autocomplete", + // options: typeOptions, + //}, ]; return searchCriteria; }, [t]); @@ -177,18 +177,18 @@ const DSOverview: React.FC = ({ type, defaultInputs }) => { ) as ScheduleType[]; const params: SearchProdSchedule = { - scheduleAt: dayjs(query?.scheduleAt).isValid() - ? query?.scheduleAt - : undefined, - schedulePeriod: dayjs(query?.schedulePeriod).isValid() - ? query?.schedulePeriod - : undefined, - schedulePeriodTo: dayjs(query?.schedulePeriodTo).isValid() - ? query?.schedulePeriodTo - : undefined, - totalEstProdCount: query?.totalEstProdCount - ? Number(query?.totalEstProdCount) - : undefined, + //scheduleAt: dayjs(query?.scheduleAt).isValid() + // ? query?.scheduleAt + // : undefined, + //schedulePeriod: dayjs(query?.schedulePeriod).isValid() + // ? query?.schedulePeriod + // : undefined, + //schedulePeriodTo: dayjs(query?.schedulePeriodTo).isValid() + // ? query?.schedulePeriodTo + // : undefined, + //totalEstProdCount: query?.totalEstProdCount + // ? Number(query?.totalEstProdCount) + // : undefined, types: convertedTypes, pageNum: pagingController.pageNum - 1, pageSize: pagingController.pageSize, @@ -311,7 +311,7 @@ const DSOverview: React.FC = ({ type, defaultInputs }) => { onClick={testDetailedScheduleClick} // disabled={filteredSchedules.some(ele => arrayToDayjs(ele.scheduleAt).isToday())} > - {t("Test Detailed Schedule")} + {t("Detailed Schedule")} = ({ // console.log(type) const apiRef = useGridApiRef(); const params = useSearchParams(); - console.log(params.get("id")); + const scheduleId = params.get("id"); // Get the schedule ID for the global release API + console.log(scheduleId); const [serverError, setServerError] = useState(""); const [tabIndex, setTabIndex] = useState(0); const { t } = useTranslation("schedule"); @@ -138,19 +141,52 @@ const DetailedScheduleDetailView: React.FC = ({ }) if (response) { - const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == row.id) - // console.log(index, formProps.getValues(`prodScheduleLines.${index}.approved`)) - // formProps.setValue(`prodScheduleLines.${index}.approved`, true) - // formProps.setValue(`prodScheduleLines.${index}.jobNo`, response.code) + // Find index of the updated line to refresh its data + // const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == row.id) + + // Update the entire line array, assuming the backend returns the updated list formProps.setValue(`prodScheduleLines`, response.entity.prodScheduleLines.sort((a, b) => b.priority - a.priority)) - // console.log(index, formProps.getValues(`prodScheduleLines.${index}.approved`)) } setIsUploading(false) } catch (e) { console.log(e) setIsUploading(false) } - }, []) + }, [formProps, setIsUploading]) + + // --- NEW FUNCTION: GLOBAL RELEASE FOR THE ENTIRE SCHEDULE --- + const onGlobalReleaseClick = useCallback(async () => { + if (!scheduleId) { + setServerError(t("Cannot release. Schedule ID is missing.")); + return; + } + + // Optional: Add a confirmation dialog here before proceeding + + setIsUploading(true); + setServerError(""); // Clear previous errors + + try { + // **IMPORTANT**: Ensure 'releaseProdSchedule' is implemented in your actions file + // to call the '/productionSchedule/detail/detailed/release' endpoint. + const response = await releaseProdSchedule({ + id: Number(scheduleId), + }) + + + if (response) { + router.refresh(); + } + + } catch (e) { + console.error(e); + setServerError(t("An unexpected error occurred during global schedule release.")); + } finally { + setIsUploading(false); + } + }, [scheduleId, setIsUploading, t, router]); + // -------------------------------------------------------------------- + const [tempValue, setTempValue] = useState(null) const onEditClick = useCallback((rowId: number) => { @@ -158,12 +194,12 @@ const DetailedScheduleDetailView: React.FC = ({ if (row) { setTempValue(row.demandQty) } - }, []) + }, [formProps]) const handleEditChange = useCallback((rowId: number, fieldName: keyof DetailedProdScheduleLineResult, newValue: number | string) => { const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(newValue)) - }, []) + }, [formProps]) const onSaveClick = useCallback(async (row: DetailedProdScheduleLineResult) => { setIsUploading(true) @@ -175,6 +211,7 @@ const DetailedScheduleDetailView: React.FC = ({ if (response) { const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == row.id) + // Update BOM materials for the line after saving demand quantity formProps.setValue(`prodScheduleLines.${index}.bomMaterials`, response.entity.bomMaterials) } setIsUploading(false) @@ -182,14 +219,15 @@ const DetailedScheduleDetailView: React.FC = ({ console.log(e) setIsUploading(false) } - }, []) + }, [formProps, setIsUploading]) const onCancelClick = useCallback(async (rowId: number) => { - // if (tempValue) { + // Revert the demandQty to the temporary value stored on EditClick + if (tempValue !== null) { const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(tempValue)) - // } - }, [tempValue]) + } + }, [formProps, tempValue]) return ( <> @@ -200,9 +238,9 @@ const DetailedScheduleDetailView: React.FC = ({ onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} > {/**/} - {/* */} - {/* {t(`${mode} ${title}`)}*/} - {/* */} + {/*  */} + {/*    {t(`${mode} ${title}`)}*/} + {/*  */} {/**/} = ({ isEditing={false} /> {/* - - */} + direction="row" + justifyContent="space-between" + flexWrap="wrap" + rowGap={2} + > + + */} {/* - - - */} + + + */} {serverError && ( {serverError} @@ -247,12 +282,24 @@ const DetailedScheduleDetailView: React.FC = ({ type={type} /> {/* {tabIndex === 1 && } */} + + {/* --- NEW BUTTON: Release Entire Schedule --- */} + + {/* ------------------------------------------- */} + {/* */} @@ -269,4 +316,4 @@ const DetailedScheduleDetailView: React.FC = ({ ); }; -export default DetailedScheduleDetailView; +export default DetailedScheduleDetailView; \ No newline at end of file diff --git a/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx b/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx index 349d90b..80a6472 100644 --- a/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx +++ b/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx @@ -17,28 +17,33 @@ const DetailedScheduleDetailWrapper: React.FC & SubComponents = async ({ id, type, }) => { - // const defaultValues = { - // id: 1, - // productionDate: "2025-05-07", - // totalJobOrders: 13, - // totalProductionQty: 21000, - // }; - - const prodSchedule = id ? await fetchDetailedProdScheduleDetail(id) : undefined - - if (prodSchedule) { - prodSchedule.prodScheduleLines = prodSchedule.prodScheduleLines.sort((a, b) => b.priority - a.priority) + const prodSchedule = id ? await fetchDetailedProdScheduleDetail(id) : undefined; + console.log("RAW API DATA:", prodSchedule?.prodScheduleLines[0]); // Check the actual keys here + + if (prodSchedule && prodSchedule.prodScheduleLines) { + // 1. Map the lines to ensure the new fields are explicitly handled + prodSchedule.prodScheduleLines = prodSchedule.prodScheduleLines.map(line => ({ + ...line, + // If the API uses different names (e.g., 'stockQty'), map them here: + // avgQtyLastMonth: line.avgQtyLastMonth ?? 0, + + // Ensure these keys match the 'field' property in your ViewByFGDetails.tsx columns + avgQtyLastMonth: line.avgQtyLastMonth || 0, + stockQty: line.stockQty || 0, + daysLeft: line.daysLeft || 0, + needNoOfJobOrder: line.needNoOfJobOrder || 0, + })).sort((a, b) => b.priority - a.priority); } + return ( ); }; DetailedScheduleDetailWrapper.Loading = GeneralLoading; -export default DetailedScheduleDetailWrapper; +export default DetailedScheduleDetailWrapper; \ No newline at end of file diff --git a/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx index 3e43eee..a4fa909 100644 --- a/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx +++ b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx @@ -30,16 +30,16 @@ type Props = { onCancelClick: (rowId: number) => void; }; -// export type FGRecord = { -// id: string | number; -// code: string; -// name: string; -// inStockQty: number; -// productionQty?: number; -// purchaseQty?: number; -// }; - -const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick, onEditClick, handleEditChange, onSaveClick, onCancelClick }) => { +const ViewByFGDetails: React.FC = ({ + apiRef, + isEdit, + type, + onReleaseClick, + onEditClick, + handleEditChange, + onSaveClick, + onCancelClick +}) => { const { t, i18n: { language }, @@ -47,83 +47,20 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick const { getValues, - watch, formState: { errors, defaultValues, touchedFields }, } = useFormContext(); - // const apiRef = useGridApiRef(); - - // const [pagingController, setPagingController] = useState([ - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // { - // pageNum: 1, - // pageSize: 10, - // totalCount: 0, - // }, - // ]); - - // const updatePagingController = (updatedObj) => { - // setPagingController((prevState) => { - // return prevState.map((item, index) => { - // if (index === updatedObj?.index) { - // return { - // ...item, - // pageNum: item.pageNum, - // pageSize: item.pageSize, - // totalCount: item.totalCount, - // }; - // } else return item; - // }); - // }); - // }; - const columns = useMemo[]>( () => [ { field: "jobNo", label: t("Job No."), type: "read-only", - // editable: true, }, { field: "code", label: t("code"), type: "read-only", - // editable: true, }, { field: "name", @@ -134,122 +71,81 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick field: "type", label: t("type"), type: "read-only", - renderCell: (row) => { - return t(row.type); - }, - // editable: true, + renderCell: (row) => <>{t(row.type)}, }, - // { - // field: "inStockQty", - // label: "Available Qty", - // type: 'read-only', - // style: { - // textAlign: "right", - // }, - // // editable: true, - // renderCell: (row: FGRecord) => { - // if (typeof (row.inStockQty) == "number") { - // return decimalFormatter.format(row.inStockQty) - // } - // return row.inStockQty - // } - // }, { field: "demandQty", label: t("Demand Qty"), type: "input-number", - style: { - textAlign: "right", - // width: "100px", - }, - renderCell: (row) => { - if (typeof row.demandQty == "number") { - return integerFormatter.format(row.demandQty ?? 0); - } - return row.demandQty; - }, + style: { textAlign: "right" } as any, // Use 'as any' to bypass strict CSS validation + renderCell: (row) => <>{integerFormatter.format(row.demandQty ?? 0)}, }, { field: "uomName", label: t("UoM"), type: "read-only", - style: { - textAlign: "left", - // width: "100px", - }, - renderCell: (row) => { - return row.uomName; - }, + renderCell: (row) => <>{row.uomName}, }, + // --- Added Avg Usage, Stock, Days Left, and Job Order Count --- { - field: "prodTimeInMinute", - label: t("Estimated Production Time"), + field: "avgQtyLastMonth", // This MUST match the key in the object + label: t("最近每日用量"), type: "read-only", - style: { - textAlign: "right", - // width: "100px", - }, - renderCell: (row) => { - return - } + // Ensure 'row' has the property 'avgQtyLastMonth' + renderCell: (row) => <>{decimalFormatter.format(row.avgQtyLastMonth ?? 0)}, }, + { + field: "stockQty", + label: t("存貨量"), + type: "read-only", + style: { textAlign: "right" } as any, + renderCell: (row) => <>{decimalFormatter.format(row.stockQty ?? 0)}, + }, + { + field: "daysLeft", + label: t("可用日"), + type: "read-only", + style: { textAlign: "right" } as any, + renderCell: (row) => <>{row.daysLeft ?? 0}, + }, + { + field: "needNoOfJobOrder", + label: t("生產量"), + type: "read-only", + style: { textAlign: "right", fontWeight: "bold" } as any, + renderCell: (row) => <>{row.needNoOfJobOrder ?? 0}, + }, + // ------------------------------------------------------------- { field: "priority", label: t("Production Priority"), type: "read-only", - style: { - textAlign: "right", - // width: "100px", - }, - // editable: true, + style: { textAlign: "right" } as any, }, ], - [], + [t] ); return ( - {/* - - {t("FG Demand List (7 Days)")} - - - index={7} - items={fakeOverallRecords} - columns={overallColumns} - setPagingController={updatePagingController} - pagingController={pagingController[7]} - isAutoPaging={false} - isEditable={false} - isEdit={isEdit} - hasCollapse={true} - /> - */} - {/* {dayPeriod.map((date, index) => ( */} - {/* - {`${t("FG Demand Date")}: ${date}`} - */} type={type} - // items={fakeRecords[index]} // Use the corresponding records for the day - items={getValues("prodScheduleLines")} // Use the corresponding records for the day + items={getValues("prodScheduleLines")} columns={columns} - // setPagingController={updatePagingController} - // pagingController={pagingController[index]} isAutoPaging={false} isEditable={true} isEdit={isEdit} hasCollapse={true} - onReleaseClick={onReleaseClick} + // Note: onReleaseClick is NOT passed here to hide the row-level "Release" function onEditClick={onEditClick} handleEditChange={handleEditChange} onSaveClick={onSaveClick} onCancelClick={onCancelClick} /> - {/* ))} */} ); -}; -export default ViewByFGDetails; +}; // Added missing closing brace + +export default ViewByFGDetails; \ No newline at end of file