浏览代码

making the autogen

master
父节点
当前提交
dc7adc9d9b
共有 6 个文件被更改,包括 205 次插入226 次删除
  1. +26
    -0
      src/app/api/scheduling/actions.ts
  2. +5
    -0
      src/app/api/scheduling/index.ts
  3. +24
    -24
      src/components/DetailedSchedule/DetailedScheduleSearchView.tsx
  4. +84
    -37
      src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx
  5. +18
    -13
      src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx
  6. +48
    -152
      src/components/DetailedScheduleDetail/ViewByFGDetails.tsx

+ 26
- 0
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<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) => {
const response = serverFetchJson<SaveProdScheduleResponse>(
`${BASE_API_URL}/productionSchedule/detail/detailed/save`,


+ 5
- 0
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 {


+ 24
- 24
src/components/DetailedSchedule/DetailedScheduleSearchView.tsx 查看文件

@@ -77,17 +77,17 @@ const DSOverview: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ type, defaultInputs }) => {
onClick={testDetailedScheduleClick}
// disabled={filteredSchedules.some(ele => arrayToDayjs(ele.scheduleAt).isToday())}
>
{t("Test Detailed Schedule")}
{t("Detailed Schedule")}
</Button>
</Stack>
<SearchBox


+ 84
- 37
src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx 查看文件

@@ -28,7 +28,9 @@ import ViewByFGDetails, {
// FGRecord,
} from "@/components/DetailedScheduleDetail/ViewByFGDetails";
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 ArrowBackIcon from '@mui/icons-material/ArrowBack';

@@ -58,7 +60,8 @@ const DetailedScheduleDetailView: React.FC<Props> = ({
// 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<Props> = ({
})

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<string | number | null>(null)
const onEditClick = useCallback((rowId: number) => {
@@ -158,12 +194,12 @@ const DetailedScheduleDetailView: React.FC<Props> = ({
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<Props> = ({

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<Props> = ({
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<Props> = ({
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
{/*<Grid>*/}
{/* <Typography mb={2} variant="h4">*/}
{/* {t(`${mode} ${title}`)}*/}
{/* </Typography>*/}
{/*  <Typography mb={2} variant="h4">*/}
{/*    {t(`${mode} ${title}`)}*/}
{/*  </Typography>*/}
{/*</Grid>*/}
<DetailInfoCard
// recordDetails={formProps.formState.defaultValues}
@@ -210,26 +248,23 @@ const DetailedScheduleDetailView: React.FC<Props> = ({
isEditing={false}
/>
{/* <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">
<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 && (
<Typography variant="body2" color="error" alignSelf="flex-end">
{serverError}
@@ -247,12 +282,24 @@ const DetailedScheduleDetailView: React.FC<Props> = ({
type={type} />
{/* {tabIndex === 1 && <ViewByBomDetails isEdit={isEdit} apiRef={apiRef} isHideButton={true} />} */}
<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
name="submit"
variant="contained"
startIcon={<Check />}
type="submit"
// disabled={submitDisabled}
//   disabled={submitDisabled}
>
{isEditMode ? t("Save") : t("Confirm")}
</Button> */}
@@ -269,4 +316,4 @@ const DetailedScheduleDetailView: React.FC<Props> = ({
</>
);
};
export default DetailedScheduleDetailView;
export default DetailedScheduleDetailView;

+ 18
- 13
src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx 查看文件

@@ -17,28 +17,33 @@ const DetailedScheduleDetailWrapper: React.FC<Props> & 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 (
<DetailedScheduleDetailView
isEditMode={Boolean(id)}
defaultValues={prodSchedule}
type={type}
// qcChecks={qcChecks || []}
/>
);
};

DetailedScheduleDetailWrapper.Loading = GeneralLoading;

export default DetailedScheduleDetailWrapper;
export default DetailedScheduleDetailWrapper;

+ 48
- 152
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<Props> = ({ apiRef, isEdit, type, onReleaseClick, onEditClick, handleEditChange, onSaveClick, onCancelClick }) => {
const ViewByFGDetails: React.FC<Props> = ({
apiRef,
isEdit,
type,
onReleaseClick,
onEditClick,
handleEditChange,
onSaveClick,
onCancelClick
}) => {
const {
t,
i18n: { language },
@@ -47,83 +47,20 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick

const {
getValues,
watch,
formState: { errors, defaultValues, touchedFields },
} = 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>[]>(
() => [
{
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<Props> = ({ 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 <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",
label: t("Production Priority"),
type: "read-only",
style: {
textAlign: "right",
// width: "100px",
},
// editable: true,
style: { textAlign: "right" } as any,
},
],
[],
[t]
);

return (
<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}>
{/* <Typography variant="overline" display="block" marginBlockEnd={1}>
{`${t("FG Demand Date")}: ${date}`}
</Typography> */}
<ScheduleTable<DetailedProdScheduleLineResult>
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}
/>
</Grid>
{/* ))} */}
</Grid>
);
};
export default ViewByFGDetails;
}; // Added missing closing brace

export default ViewByFGDetails;

正在加载...
取消
保存