| @@ -39,6 +39,10 @@ export interface ReleaseProdScheduleInputs { | |||||
| demandQty: number; | demandQty: number; | ||||
| } | } | ||||
| export interface ReleaseProdScheduleReq { | |||||
| id: number; | |||||
| } | |||||
| export interface ReleaseProdScheduleResponse { | export interface ReleaseProdScheduleResponse { | ||||
| id: number; | id: number; | ||||
| code: string; | code: string; | ||||
| @@ -48,6 +52,12 @@ export interface ReleaseProdScheduleResponse { | |||||
| message: string; | message: string; | ||||
| } | } | ||||
| export interface ReleaseProdScheduleRsp { | |||||
| id: number; | |||||
| code: string; | |||||
| message: string; | |||||
| } | |||||
| export interface SaveProdScheduleResponse { | export interface SaveProdScheduleResponse { | ||||
| id: number; | id: number; | ||||
| code: string; | code: string; | ||||
| @@ -151,6 +161,22 @@ export const releaseProdScheduleLine = cache(async (data: ReleaseProdScheduleInp | |||||
| return response; | return response; | ||||
| }) | }) | ||||
| export const releaseProdSchedule = cache(async (data: ReleaseProdScheduleReq) => { | |||||
| const response = serverFetchJson<ReleaseProdScheduleRsp>( | |||||
| `${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) => { | export const saveProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => { | ||||
| const response = serverFetchJson<SaveProdScheduleResponse>( | const response = serverFetchJson<SaveProdScheduleResponse>( | ||||
| `${BASE_API_URL}/productionSchedule/detail/detailed/save`, | `${BASE_API_URL}/productionSchedule/detail/detailed/save`, | ||||
| @@ -100,6 +100,11 @@ export interface DetailedProdScheduleLineResult { | |||||
| priority: number; | priority: number; | ||||
| approved: boolean; | approved: boolean; | ||||
| proportion: number; | 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 { | export interface DetailedProdScheduleLineBomMaterialResult { | ||||
| @@ -77,17 +77,17 @@ const DSOverview: React.FC<Props> = ({ type, defaultInputs }) => { | |||||
| // type: "dateRange", | // type: "dateRange", | ||||
| // }, | // }, | ||||
| { label: t("Production Date"), paramName: "scheduleAt", type: "date" }, | { 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; | return searchCriteria; | ||||
| }, [t]); | }, [t]); | ||||
| @@ -177,18 +177,18 @@ const DSOverview: React.FC<Props> = ({ type, defaultInputs }) => { | |||||
| ) as ScheduleType[]; | ) as ScheduleType[]; | ||||
| const params: SearchProdSchedule = { | 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, | types: convertedTypes, | ||||
| pageNum: pagingController.pageNum - 1, | pageNum: pagingController.pageNum - 1, | ||||
| pageSize: pagingController.pageSize, | pageSize: pagingController.pageSize, | ||||
| @@ -311,7 +311,7 @@ const DSOverview: React.FC<Props> = ({ type, defaultInputs }) => { | |||||
| onClick={testDetailedScheduleClick} | onClick={testDetailedScheduleClick} | ||||
| // disabled={filteredSchedules.some(ele => arrayToDayjs(ele.scheduleAt).isToday())} | // disabled={filteredSchedules.some(ele => arrayToDayjs(ele.scheduleAt).isToday())} | ||||
| > | > | ||||
| {t("Test Detailed Schedule")} | |||||
| {t("Detailed Schedule")} | |||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| <SearchBox | <SearchBox | ||||
| @@ -28,7 +28,9 @@ import ViewByFGDetails, { | |||||
| // FGRecord, | // FGRecord, | ||||
| } from "@/components/DetailedScheduleDetail/ViewByFGDetails"; | } from "@/components/DetailedScheduleDetail/ViewByFGDetails"; | ||||
| import { DetailedProdScheduleLineResult, DetailedProdScheduleResult, ScheduleType } from "@/app/api/scheduling"; | import { DetailedProdScheduleLineResult, DetailedProdScheduleResult, ScheduleType } from "@/app/api/scheduling"; | ||||
| import { releaseProdScheduleLine, saveProdScheduleLine } from "@/app/api/scheduling/actions"; | |||||
| // NOTE: Assuming 'releaseProdSchedule' is the new action function | |||||
| // you need to implement to call the '/productionSchedule/detail/detailed/release' API | |||||
| import { releaseProdScheduleLine, saveProdScheduleLine, releaseProdSchedule } from "@/app/api/scheduling/actions"; | |||||
| import useUploadContext from "../UploadProvider/useUploadContext"; | import useUploadContext from "../UploadProvider/useUploadContext"; | ||||
| import ArrowBackIcon from '@mui/icons-material/ArrowBack'; | import ArrowBackIcon from '@mui/icons-material/ArrowBack'; | ||||
| @@ -58,7 +60,8 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| // console.log(type) | // console.log(type) | ||||
| const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
| const params = useSearchParams(); | 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 [serverError, setServerError] = useState(""); | ||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const { t } = useTranslation("schedule"); | const { t } = useTranslation("schedule"); | ||||
| @@ -138,19 +141,52 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| }) | }) | ||||
| if (response) { | 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)) | formProps.setValue(`prodScheduleLines`, response.entity.prodScheduleLines.sort((a, b) => b.priority - a.priority)) | ||||
| // console.log(index, formProps.getValues(`prodScheduleLines.${index}.approved`)) | |||||
| } | } | ||||
| setIsUploading(false) | setIsUploading(false) | ||||
| } catch (e) { | } catch (e) { | ||||
| console.log(e) | console.log(e) | ||||
| setIsUploading(false) | 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<string | number | null>(null) | const [tempValue, setTempValue] = useState<string | number | null>(null) | ||||
| const onEditClick = useCallback((rowId: number) => { | const onEditClick = useCallback((rowId: number) => { | ||||
| @@ -158,12 +194,12 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| if (row) { | if (row) { | ||||
| setTempValue(row.demandQty) | setTempValue(row.demandQty) | ||||
| } | } | ||||
| }, []) | |||||
| }, [formProps]) | |||||
| const handleEditChange = useCallback((rowId: number, fieldName: keyof DetailedProdScheduleLineResult, newValue: number | string) => { | const handleEditChange = useCallback((rowId: number, fieldName: keyof DetailedProdScheduleLineResult, newValue: number | string) => { | ||||
| const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) | const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) | ||||
| formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(newValue)) | formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(newValue)) | ||||
| }, []) | |||||
| }, [formProps]) | |||||
| const onSaveClick = useCallback(async (row: DetailedProdScheduleLineResult) => { | const onSaveClick = useCallback(async (row: DetailedProdScheduleLineResult) => { | ||||
| setIsUploading(true) | setIsUploading(true) | ||||
| @@ -175,6 +211,7 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| if (response) { | if (response) { | ||||
| const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == row.id) | 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) | formProps.setValue(`prodScheduleLines.${index}.bomMaterials`, response.entity.bomMaterials) | ||||
| } | } | ||||
| setIsUploading(false) | setIsUploading(false) | ||||
| @@ -182,14 +219,15 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| console.log(e) | console.log(e) | ||||
| setIsUploading(false) | setIsUploading(false) | ||||
| } | } | ||||
| }, []) | |||||
| }, [formProps, setIsUploading]) | |||||
| const onCancelClick = useCallback(async (rowId: number) => { | 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) | const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) | ||||
| formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(tempValue)) | formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(tempValue)) | ||||
| // } | |||||
| }, [tempValue]) | |||||
| } | |||||
| }, [formProps, tempValue]) | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -200,9 +238,9 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | ||||
| > | > | ||||
| {/*<Grid>*/} | {/*<Grid>*/} | ||||
| {/* <Typography mb={2} variant="h4">*/} | |||||
| {/* {t(`${mode} ${title}`)}*/} | |||||
| {/* </Typography>*/} | |||||
| {/* <Typography mb={2} variant="h4">*/} | |||||
| {/* {t(`${mode} ${title}`)}*/} | |||||
| {/* </Typography>*/} | |||||
| {/*</Grid>*/} | {/*</Grid>*/} | ||||
| <DetailInfoCard | <DetailInfoCard | ||||
| // recordDetails={formProps.formState.defaultValues} | // recordDetails={formProps.formState.defaultValues} | ||||
| @@ -210,26 +248,23 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| isEditing={false} | isEditing={false} | ||||
| /> | /> | ||||
| {/* <Stack | {/* <Stack | ||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Button | |||||
| variant="contained" | |||||
| onClick={onClickEdit} | |||||
| // startIcon={<Add />} | |||||
| //LinkComponent={Link} | |||||
| //href="qcCategory/create" | |||||
| > | |||||
| {isEdit ? t("Save") : t("Edit")} | |||||
| </Button> | |||||
| </Stack> */} | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Button | |||||
| variant="contained" | |||||
| onClick={onClickEdit} | |||||
| > | |||||
| {isEdit ? t("Save") : t("Edit")} | |||||
| </Button> | |||||
| </Stack> */} | |||||
| {/* <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | {/* <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | ||||
| <Tab label={t("View By FG") + (tabIndex === 0 ? " (Selected)" : "")} iconPosition="end" /> | |||||
| <Tab label={t("View By Material") + (tabIndex === 1 ? " (Selected)" : "")} iconPosition="end" /> | |||||
| </Tabs> */} | |||||
| <Tab label={t("View By FG") + (tabIndex === 0 ? " (Selected)" : "")} iconPosition="end" /> | |||||
| <Tab label={t("View By Material") + (tabIndex === 1 ? " (Selected)" : "")} iconPosition="end" /> | |||||
| </Tabs> */} | |||||
| {serverError && ( | {serverError && ( | ||||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | <Typography variant="body2" color="error" alignSelf="flex-end"> | ||||
| {serverError} | {serverError} | ||||
| @@ -247,12 +282,24 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| type={type} /> | type={type} /> | ||||
| {/* {tabIndex === 1 && <ViewByBomDetails isEdit={isEdit} apiRef={apiRef} isHideButton={true} />} */} | {/* {tabIndex === 1 && <ViewByBomDetails isEdit={isEdit} apiRef={apiRef} isHideButton={true} />} */} | ||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| {/* --- NEW BUTTON: Release Entire Schedule --- */} | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Check />} | |||||
| onClick={onGlobalReleaseClick} | |||||
| disabled={!scheduleId} // Disable if we don't have a schedule ID | |||||
| > | |||||
| {t("Release Schedule")} | |||||
| </Button> | |||||
| {/* ------------------------------------------- */} | |||||
| {/* <Button | {/* <Button | ||||
| name="submit" | name="submit" | ||||
| variant="contained" | variant="contained" | ||||
| startIcon={<Check />} | startIcon={<Check />} | ||||
| type="submit" | type="submit" | ||||
| // disabled={submitDisabled} | |||||
| // disabled={submitDisabled} | |||||
| > | > | ||||
| {isEditMode ? t("Save") : t("Confirm")} | {isEditMode ? t("Save") : t("Confirm")} | ||||
| </Button> */} | </Button> */} | ||||
| @@ -269,4 +316,4 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default DetailedScheduleDetailView; | |||||
| export default DetailedScheduleDetailView; | |||||
| @@ -17,28 +17,33 @@ const DetailedScheduleDetailWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| id, | id, | ||||
| type, | 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 ( | return ( | ||||
| <DetailedScheduleDetailView | <DetailedScheduleDetailView | ||||
| isEditMode={Boolean(id)} | isEditMode={Boolean(id)} | ||||
| defaultValues={prodSchedule} | defaultValues={prodSchedule} | ||||
| type={type} | type={type} | ||||
| // qcChecks={qcChecks || []} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||
| DetailedScheduleDetailWrapper.Loading = GeneralLoading; | DetailedScheduleDetailWrapper.Loading = GeneralLoading; | ||||
| export default DetailedScheduleDetailWrapper; | |||||
| export default DetailedScheduleDetailWrapper; | |||||
| @@ -30,16 +30,16 @@ type Props = { | |||||
| onCancelClick: (rowId: number) => void; | onCancelClick: (rowId: number) => void; | ||||
| }; | }; | ||||
| // export type FGRecord = { | |||||
| // id: string | number; | |||||
| // code: string; | |||||
| // name: string; | |||||
| // inStockQty: number; | |||||
| // productionQty?: number; | |||||
| // purchaseQty?: number; | |||||
| // }; | |||||
| const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick, onEditClick, handleEditChange, onSaveClick, onCancelClick }) => { | |||||
| const ViewByFGDetails: React.FC<Props> = ({ | |||||
| apiRef, | |||||
| isEdit, | |||||
| type, | |||||
| onReleaseClick, | |||||
| onEditClick, | |||||
| handleEditChange, | |||||
| onSaveClick, | |||||
| onCancelClick | |||||
| }) => { | |||||
| const { | const { | ||||
| t, | t, | ||||
| i18n: { language }, | i18n: { language }, | ||||
| @@ -47,83 +47,20 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||||
| const { | const { | ||||
| getValues, | getValues, | ||||
| watch, | |||||
| formState: { errors, defaultValues, touchedFields }, | formState: { errors, defaultValues, touchedFields }, | ||||
| } = useFormContext<DetailedProdScheduleResult>(); | } = useFormContext<DetailedProdScheduleResult>(); | ||||
| // 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<Column<DetailedProdScheduleLineResult>[]>( | const columns = useMemo<Column<DetailedProdScheduleLineResult>[]>( | ||||
| () => [ | () => [ | ||||
| { | { | ||||
| field: "jobNo", | field: "jobNo", | ||||
| label: t("Job No."), | label: t("Job No."), | ||||
| type: "read-only", | type: "read-only", | ||||
| // editable: true, | |||||
| }, | }, | ||||
| { | { | ||||
| field: "code", | field: "code", | ||||
| label: t("code"), | label: t("code"), | ||||
| type: "read-only", | type: "read-only", | ||||
| // editable: true, | |||||
| }, | }, | ||||
| { | { | ||||
| field: "name", | field: "name", | ||||
| @@ -134,122 +71,81 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||||
| field: "type", | field: "type", | ||||
| label: t("type"), | label: t("type"), | ||||
| type: "read-only", | 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", | field: "demandQty", | ||||
| label: t("Demand Qty"), | label: t("Demand Qty"), | ||||
| type: "input-number", | 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", | field: "uomName", | ||||
| label: t("UoM"), | label: t("UoM"), | ||||
| type: "read-only", | 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", | type: "read-only", | ||||
| style: { | |||||
| textAlign: "right", | |||||
| // width: "100px", | |||||
| }, | |||||
| renderCell: (row) => { | |||||
| return <ProdTimeColumn prodTimeInMinute={row.prodTimeInMinute} /> | |||||
| } | |||||
| // 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", | field: "priority", | ||||
| label: t("Production Priority"), | label: t("Production Priority"), | ||||
| type: "read-only", | type: "read-only", | ||||
| style: { | |||||
| textAlign: "right", | |||||
| // width: "100px", | |||||
| }, | |||||
| // editable: true, | |||||
| style: { textAlign: "right" } as any, | |||||
| }, | }, | ||||
| ], | ], | ||||
| [], | |||||
| [t] | |||||
| ); | ); | ||||
| return ( | return ( | ||||
| <Grid container spacing={2}> | <Grid container spacing={2}> | ||||
| {/* <Grid item xs={12} key={"all"}> | |||||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
| {t("FG Demand List (7 Days)")} | |||||
| </Typography> | |||||
| <EditableSearchResults<FGRecord> | |||||
| index={7} | |||||
| items={fakeOverallRecords} | |||||
| columns={overallColumns} | |||||
| setPagingController={updatePagingController} | |||||
| pagingController={pagingController[7]} | |||||
| isAutoPaging={false} | |||||
| isEditable={false} | |||||
| isEdit={isEdit} | |||||
| hasCollapse={true} | |||||
| /> | |||||
| </Grid> */} | |||||
| {/* {dayPeriod.map((date, index) => ( */} | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| {/* <Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
| {`${t("FG Demand Date")}: ${date}`} | |||||
| </Typography> */} | |||||
| <ScheduleTable<DetailedProdScheduleLineResult> | <ScheduleTable<DetailedProdScheduleLineResult> | ||||
| type={type} | 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} | columns={columns} | ||||
| // setPagingController={updatePagingController} | |||||
| // pagingController={pagingController[index]} | |||||
| isAutoPaging={false} | isAutoPaging={false} | ||||
| isEditable={true} | isEditable={true} | ||||
| isEdit={isEdit} | isEdit={isEdit} | ||||
| hasCollapse={true} | hasCollapse={true} | ||||
| onReleaseClick={onReleaseClick} | |||||
| // Note: onReleaseClick is NOT passed here to hide the row-level "Release" function | |||||
| onEditClick={onEditClick} | onEditClick={onEditClick} | ||||
| handleEditChange={handleEditChange} | handleEditChange={handleEditChange} | ||||
| onSaveClick={onSaveClick} | onSaveClick={onSaveClick} | ||||
| onCancelClick={onCancelClick} | onCancelClick={onCancelClick} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| {/* ))} */} | |||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| }; | |||||
| export default ViewByFGDetails; | |||||
| }; // Added missing closing brace | |||||
| export default ViewByFGDetails; | |||||