| @@ -4,7 +4,8 @@ import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { cache } from "react"; | import { cache } from "react"; | ||||
| import { ScheduleType } from "."; | |||||
| import { DetailedProdScheduleLineBomMaterialResult, DetailedProdScheduleLineResult, ScheduleType } from "."; | |||||
| import { revalidateTag } from "next/cache"; | |||||
| export interface SearchProdSchedule { | export interface SearchProdSchedule { | ||||
| scheduleAt?: string; | scheduleAt?: string; | ||||
| @@ -31,11 +32,29 @@ export interface ProdScheduleResultByPage { | |||||
| records: ProdScheduleResult[]; | records: ProdScheduleResult[]; | ||||
| } | } | ||||
| export interface ReleaseDetailProdScheduleInputs { | |||||
| export interface ReleaseProdScheduleInputs { | |||||
| id: number; | id: number; | ||||
| demandQty: number; | demandQty: number; | ||||
| } | } | ||||
| export interface ReleaseProdScheduleResponse { | |||||
| id: number; | |||||
| code: string; | |||||
| entity: { | |||||
| prodScheduleLines: DetailedProdScheduleLineResult[]; | |||||
| }; | |||||
| message: string; | |||||
| } | |||||
| export interface SaveProdScheduleResponse { | |||||
| id: number; | |||||
| code: string; | |||||
| entity: { | |||||
| bomMaterials: DetailedProdScheduleLineBomMaterialResult[] | |||||
| }; | |||||
| message: string; | |||||
| } | |||||
| export const fetchProdSchedules = cache( | export const fetchProdSchedules = cache( | ||||
| async (data: SearchProdSchedule | null) => { | async (data: SearchProdSchedule | null) => { | ||||
| const params = convertObjToURLSearchParams<SearchProdSchedule>(data); | const params = convertObjToURLSearchParams<SearchProdSchedule>(data); | ||||
| @@ -79,16 +98,32 @@ export const testDetailedSchedule = cache(async () => { | |||||
| ); | ); | ||||
| }); | }); | ||||
| export const releaseProdScheduleLine = cache(async (data: ReleaseDetailProdScheduleInputs) => { | |||||
| return serverFetchJson( | |||||
| `${BASE_API_URL}/productionSchedule/releaseLine`, | |||||
| export const releaseProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => { | |||||
| const response = serverFetchJson<ReleaseProdScheduleResponse>( | |||||
| `${BASE_API_URL}/productionSchedule/detail/detailed/releaseLine`, | |||||
| { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| } | |||||
| ); | |||||
| revalidateTag("prodSchedules"); | |||||
| return response; | |||||
| }) | |||||
| export const saveProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => { | |||||
| const response = serverFetchJson<SaveProdScheduleResponse>( | |||||
| `${BASE_API_URL}/productionSchedule/detail/detailed/save`, | |||||
| { | { | ||||
| method: "POST", | method: "POST", | ||||
| body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
| headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
| next: { | |||||
| tags: ["prodSchedules"], | |||||
| }, | |||||
| } | } | ||||
| ) | |||||
| ); | |||||
| revalidateTag("prodSchedules"); | |||||
| return response; | |||||
| }) | }) | ||||
| @@ -93,8 +93,11 @@ export interface DetailedProdScheduleLineResult { | |||||
| name: string; | name: string; | ||||
| type: string; | type: string; | ||||
| demandQty: number; | demandQty: number; | ||||
| bomOutputQty: number; | |||||
| prodTimeInMinute: DetailedProdScheduleLineProdTimeResult[]; | prodTimeInMinute: DetailedProdScheduleLineProdTimeResult[]; | ||||
| priority: number; | priority: number; | ||||
| approved: boolean; | |||||
| proportion: number; | |||||
| } | } | ||||
| export interface DetailedProdScheduleLineBomMaterialResult { | export interface DetailedProdScheduleLineBomMaterialResult { | ||||
| @@ -23,6 +23,7 @@ export const moneyFormatter = new Intl.NumberFormat("en-HK", { | |||||
| export const decimalFormatter = new Intl.NumberFormat("en-HK", { | export const decimalFormatter = new Intl.NumberFormat("en-HK", { | ||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | |||||
| }); | }); | ||||
| export const integerFormatter = new Intl.NumberFormat("en-HK", {}); | export const integerFormatter = new Intl.NumberFormat("en-HK", {}); | ||||
| @@ -7,6 +7,7 @@ import { | |||||
| FormProvider, | FormProvider, | ||||
| SubmitErrorHandler, | SubmitErrorHandler, | ||||
| SubmitHandler, | SubmitHandler, | ||||
| useFieldArray, | |||||
| useForm, | useForm, | ||||
| } from "react-hook-form"; | } from "react-hook-form"; | ||||
| import { | import { | ||||
| @@ -26,7 +27,10 @@ import DetailInfoCard from "@/components/DetailedScheduleDetail/DetailInfoCard"; | |||||
| import ViewByFGDetails, { | import ViewByFGDetails, { | ||||
| // FGRecord, | // FGRecord, | ||||
| } from "@/components/DetailedScheduleDetail/ViewByFGDetails"; | } from "@/components/DetailedScheduleDetail/ViewByFGDetails"; | ||||
| import { DetailedProdScheduleResult, ScheduleType } from "@/app/api/scheduling"; | |||||
| import { DetailedProdScheduleLineResult, DetailedProdScheduleResult, ScheduleType } from "@/app/api/scheduling"; | |||||
| import { releaseProdScheduleLine, saveProdScheduleLine } from "@/app/api/scheduling/actions"; | |||||
| import useUploadContext from "../UploadProvider/useUploadContext"; | |||||
| import ArrowBackIcon from '@mui/icons-material/ArrowBack'; | |||||
| // temp interface input | // temp interface input | ||||
| // export interface SaveDetailedSchedule { | // export interface SaveDetailedSchedule { | ||||
| @@ -59,14 +63,20 @@ const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const { t } = useTranslation("schedule"); | const { t } = useTranslation("schedule"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [isEdit, setIsEdit] = useState(false); | |||||
| const [isEdit, setIsEdit] = useState(false); | |||||
| const { setIsUploading } = useUploadContext() | |||||
| // console.log(typeId) | // console.log(typeId) | ||||
| const formProps = useForm<DetailedProdScheduleResult>({ | const formProps = useForm<DetailedProdScheduleResult>({ | ||||
| defaultValues: defaultValues | defaultValues: defaultValues | ||||
| }); | }); | ||||
| const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
| const lineFormProps = useFieldArray<DetailedProdScheduleResult>({ | |||||
| control: formProps.control, | |||||
| name: "prodScheduleLines" | |||||
| }) | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | ||||
| (_e, newValue) => { | (_e, newValue) => { | ||||
| setTabIndex(newValue); | setTabIndex(newValue); | ||||
| @@ -74,6 +84,10 @@ const [isEdit, setIsEdit] = useState(false); | |||||
| [], | [], | ||||
| ); | ); | ||||
| // const calNewProportion = useCallback((demandQty: number, bomOutputQty: number) => { | |||||
| // return ((demandQty ?? 0) / (bomOutputQty ?? 1)).toFixed(2) | |||||
| // }, []) | |||||
| // const [pagingController, setPagingController] = useState({ | // const [pagingController, setPagingController] = useState({ | ||||
| // pageNum: 1, | // pageNum: 1, | ||||
| // pageSize: 10, | // pageSize: 10, | ||||
| @@ -81,7 +95,7 @@ const [isEdit, setIsEdit] = useState(false); | |||||
| // }); | // }); | ||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| router.replace(`/scheduling/Detail`); | |||||
| router.replace(`/scheduling/detailed`); | |||||
| }; | }; | ||||
| const onSubmit = useCallback<SubmitHandler<DetailedProdScheduleResult>>( | const onSubmit = useCallback<SubmitHandler<DetailedProdScheduleResult>>( | ||||
| @@ -106,7 +120,7 @@ const [isEdit, setIsEdit] = useState(false); | |||||
| // multiple tabs | // multiple tabs | ||||
| const onSubmitError = useCallback<SubmitErrorHandler<DetailedProdScheduleResult>>( | const onSubmitError = useCallback<SubmitErrorHandler<DetailedProdScheduleResult>>( | ||||
| (errors) => {}, | |||||
| (errors) => { }, | |||||
| [], | [], | ||||
| ); | ); | ||||
| @@ -114,10 +128,69 @@ const [isEdit, setIsEdit] = useState(false); | |||||
| setIsEdit(!isEdit); | setIsEdit(!isEdit); | ||||
| }; | }; | ||||
| const onReleaseClick = useCallback(() => { | |||||
| const onReleaseClick = useCallback(async (row: DetailedProdScheduleLineResult) => { | |||||
| setIsUploading(true) | |||||
| try { | |||||
| const response = await releaseProdScheduleLine({ | |||||
| id: row.id, | |||||
| demandQty: row.demandQty | |||||
| }) | |||||
| 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) | |||||
| 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) | |||||
| } | |||||
| }, []) | |||||
| const [tempValue, setTempValue] = useState<string | number | null>(null) | |||||
| const onEditClick = useCallback((rowId: number) => { | |||||
| const row = formProps.getValues("prodScheduleLines").find(ele => ele.id == rowId) | |||||
| if (row) { | |||||
| setTempValue(row.demandQty) | |||||
| } | |||||
| }, []) | |||||
| 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)) | |||||
| }, []) | }, []) | ||||
| const onSaveClick = useCallback(async (row: DetailedProdScheduleLineResult) => { | |||||
| setIsUploading(true) | |||||
| try { | |||||
| const response = await saveProdScheduleLine({ | |||||
| id: row.id, | |||||
| demandQty: row.demandQty | |||||
| }) | |||||
| if (response) { | |||||
| const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == row.id) | |||||
| formProps.setValue(`prodScheduleLines.${index}.bomMaterials`, response.entity.bomMaterials) | |||||
| } | |||||
| setIsUploading(false) | |||||
| } catch (e) { | |||||
| console.log(e) | |||||
| setIsUploading(false) | |||||
| } | |||||
| }, []) | |||||
| const onCancelClick = useCallback(async (rowId: number) => { | |||||
| // if (tempValue) { | |||||
| const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) | |||||
| formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(tempValue)) | |||||
| // } | |||||
| }, [tempValue]) | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
| @@ -133,9 +206,10 @@ const [isEdit, setIsEdit] = useState(false); | |||||
| {/*</Grid>*/} | {/*</Grid>*/} | ||||
| <DetailInfoCard | <DetailInfoCard | ||||
| // recordDetails={formProps.formState.defaultValues} | // recordDetails={formProps.formState.defaultValues} | ||||
| isEditing={isEdit} | |||||
| // isEditing={isEdit} | |||||
| isEditing={false} | |||||
| /> | /> | ||||
| <Stack | |||||
| {/* <Stack | |||||
| direction="row" | direction="row" | ||||
| justifyContent="space-between" | justifyContent="space-between" | ||||
| flexWrap="wrap" | flexWrap="wrap" | ||||
| @@ -144,13 +218,13 @@ const [isEdit, setIsEdit] = useState(false); | |||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| onClick={onClickEdit} | onClick={onClickEdit} | ||||
| // startIcon={<Add />} | |||||
| //LinkComponent={Link} | |||||
| //href="qcCategory/create" | |||||
| // startIcon={<Add />} | |||||
| //LinkComponent={Link} | |||||
| //href="qcCategory/create" | |||||
| > | > | ||||
| {isEdit ? t("Save") : t("Edit")} | {isEdit ? t("Save") : t("Edit")} | ||||
| </Button> | </Button> | ||||
| </Stack> | |||||
| </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 FG") + (tabIndex === 0 ? " (Selected)" : "")} iconPosition="end" /> | ||||
| @@ -162,24 +236,32 @@ const [isEdit, setIsEdit] = useState(false); | |||||
| </Typography> | </Typography> | ||||
| )} | )} | ||||
| {/* {tabIndex === 0 && <ViewByFGDetails isEdit={isEdit} apiRef={apiRef} />} */} | {/* {tabIndex === 0 && <ViewByFGDetails isEdit={isEdit} apiRef={apiRef} />} */} | ||||
| <ViewByFGDetails isEdit={isEdit} apiRef={apiRef} onReleaseClick={onReleaseClick} type={type}/> | |||||
| <ViewByFGDetails | |||||
| isEdit={true} | |||||
| apiRef={apiRef} | |||||
| onReleaseClick={onReleaseClick} | |||||
| onEditClick={onEditClick} | |||||
| handleEditChange={handleEditChange} | |||||
| onSaveClick={onSaveClick} | |||||
| onCancelClick={onCancelClick} | |||||
| 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}> | ||||
| <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> */} | |||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| startIcon={<Close />} | |||||
| startIcon={<ArrowBackIcon />} | |||||
| onClick={handleCancel} | onClick={handleCancel} | ||||
| > | > | ||||
| {t("Cancel")} | |||||
| {t("Back")} | |||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| </Stack> | </Stack> | ||||
| @@ -23,7 +23,11 @@ type Props = { | |||||
| apiRef: MutableRefObject<GridApiCommunity>; | apiRef: MutableRefObject<GridApiCommunity>; | ||||
| isEdit: boolean; | isEdit: boolean; | ||||
| type: ScheduleType; | type: ScheduleType; | ||||
| onReleaseClick: () => void; | |||||
| onReleaseClick: (item: DetailedProdScheduleLineResult) => void; | |||||
| onEditClick: (rowId: number) => void; | |||||
| handleEditChange: (rowId: number, fieldName: keyof DetailedProdScheduleLineResult, newValue: number | string) => void; | |||||
| onSaveClick: (item: DetailedProdScheduleLineResult) => void; | |||||
| onCancelClick: (rowId: number) => void; | |||||
| }; | }; | ||||
| // export type FGRecord = { | // export type FGRecord = { | ||||
| @@ -35,7 +39,7 @@ type Props = { | |||||
| // purchaseQty?: number; | // purchaseQty?: number; | ||||
| // }; | // }; | ||||
| const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick }) => { | |||||
| const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick, onEditClick, handleEditChange, onSaveClick, onCancelClick }) => { | |||||
| const { | const { | ||||
| t, | t, | ||||
| i18n: { language }, | i18n: { language }, | ||||
| @@ -43,8 +47,10 @@ 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 apiRef = useGridApiRef(); | ||||
| // const [pagingController, setPagingController] = useState([ | // const [pagingController, setPagingController] = useState([ | ||||
| @@ -128,6 +134,9 @@ 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, | // editable: true, | ||||
| }, | }, | ||||
| // { | // { | ||||
| @@ -148,7 +157,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||||
| { | { | ||||
| field: "demandQty", | field: "demandQty", | ||||
| label: t("Demand Qty"), | label: t("Demand Qty"), | ||||
| type: "input", | |||||
| type: "input-number", | |||||
| style: { | style: { | ||||
| textAlign: "right", | textAlign: "right", | ||||
| }, | }, | ||||
| @@ -202,23 +211,28 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||||
| /> | /> | ||||
| </Grid> */} | </Grid> */} | ||||
| {/* {dayPeriod.map((date, index) => ( */} | {/* {dayPeriod.map((date, index) => ( */} | ||||
| <Grid item xs={12}> | |||||
| {/* <Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
| <Grid item xs={12}> | |||||
| {/* <Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
| {`${t("FG Demand Date")}: ${date}`} | {`${t("FG Demand Date")}: ${date}`} | ||||
| </Typography> */} | </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 | |||||
| columns={columns} | |||||
| // setPagingController={updatePagingController} | |||||
| // pagingController={pagingController[index]} | |||||
| isAutoPaging={false} | |||||
| isEditable={true} | |||||
| isEdit={isEdit} | |||||
| hasCollapse={true} | |||||
| /> | |||||
| </Grid> | |||||
| <ScheduleTable<DetailedProdScheduleLineResult> | |||||
| type={type} | |||||
| // items={fakeRecords[index]} // Use the corresponding records for the day | |||||
| items={getValues("prodScheduleLines")} // Use the corresponding records for the day | |||||
| columns={columns} | |||||
| // setPagingController={updatePagingController} | |||||
| // pagingController={pagingController[index]} | |||||
| isAutoPaging={false} | |||||
| isEditable={true} | |||||
| isEdit={isEdit} | |||||
| hasCollapse={true} | |||||
| onReleaseClick={onReleaseClick} | |||||
| onEditClick={onEditClick} | |||||
| handleEditChange={handleEditChange} | |||||
| onSaveClick={onSaveClick} | |||||
| onCancelClick={onCancelClick} | |||||
| /> | |||||
| </Grid> | |||||
| {/* ))} */} | {/* ))} */} | ||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| @@ -5,6 +5,7 @@ import React, { | |||||
| DetailedHTMLProps, | DetailedHTMLProps, | ||||
| HTMLAttributes, | HTMLAttributes, | ||||
| useEffect, | useEffect, | ||||
| useRef, | |||||
| useState, | useState, | ||||
| } from "react"; | } from "react"; | ||||
| import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||
| @@ -37,6 +38,7 @@ import { | |||||
| ScheduleType, | ScheduleType, | ||||
| } from "@/app/api/scheduling"; | } from "@/app/api/scheduling"; | ||||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | import { defaultPagingController } from "../SearchResults/SearchResults"; | ||||
| import { useFormContext } from "react-hook-form"; | |||||
| export interface ResultWithId { | export interface ResultWithId { | ||||
| id: string | number; | id: string | number; | ||||
| @@ -81,6 +83,11 @@ interface Props<T extends ResultWithId> { | |||||
| isEditable: boolean; | isEditable: boolean; | ||||
| hasCollapse: boolean; | hasCollapse: boolean; | ||||
| type: ScheduleType; | type: ScheduleType; | ||||
| onReleaseClick?: (item: T) => void; | |||||
| onEditClick?: (rowId: number) => void; | |||||
| handleEditChange?: (rowId: number, fieldName: keyof T, newValue: number | string) => void; | |||||
| onSaveClick?: (item: T) => void; | |||||
| onCancelClick?: (rowId: number) => void; | |||||
| } | } | ||||
| function ScheduleTable<T extends ResultWithId>({ | function ScheduleTable<T extends ResultWithId>({ | ||||
| @@ -95,15 +102,23 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| isEdit = false, | isEdit = false, | ||||
| isEditable = true, | isEditable = true, | ||||
| hasCollapse = false, | hasCollapse = false, | ||||
| onReleaseClick = undefined, | |||||
| onEditClick = undefined, | |||||
| handleEditChange = undefined, | |||||
| onSaveClick = undefined, | |||||
| onCancelClick = undefined, | |||||
| }: Props<T>) { | }: Props<T>) { | ||||
| const [page, setPage] = useState(0); | const [page, setPage] = useState(0); | ||||
| const [rowsPerPage, setRowsPerPage] = useState(10); | const [rowsPerPage, setRowsPerPage] = useState(10); | ||||
| const [editingRowId, setEditingRowId] = useState<number | null>(null); | const [editingRowId, setEditingRowId] = useState<number | null>(null); | ||||
| const [editedItems, setEditedItems] = useState<T[]>(items); | const [editedItems, setEditedItems] = useState<T[]>(items); | ||||
| const { t } = useTranslation("schedule"); | const { t } = useTranslation("schedule"); | ||||
| console.log(items) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setEditedItems(items); | setEditedItems(items); | ||||
| }, [items]); | }, [items]); | ||||
| const handleChangePage = (_event: unknown, newPage: number) => { | const handleChangePage = (_event: unknown, newPage: number) => { | ||||
| setPage(newPage); | setPage(newPage); | ||||
| if (setPagingController && pagingController) { | if (setPagingController && pagingController) { | ||||
| @@ -132,24 +147,42 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| const handleEditClick = (id: number) => { | const handleEditClick = (id: number) => { | ||||
| setEditingRowId(id); | setEditingRowId(id); | ||||
| if (onEditClick) { | |||||
| onEditClick(id) | |||||
| } | |||||
| }; | }; | ||||
| const handleSaveClick = (item: T) => { | const handleSaveClick = (item: T) => { | ||||
| setEditingRowId(null); | setEditingRowId(null); | ||||
| // Call API or any save logic here | // Call API or any save logic here | ||||
| setEditedItems((prev) => | |||||
| prev.map((row) => (row.id === item.id ? { ...row } : row)), | |||||
| ); | |||||
| if (onSaveClick) { | |||||
| onSaveClick(item) | |||||
| } else { | |||||
| setEditedItems((prev) => | |||||
| prev.map((row) => (row.id === item.id ? { ...row } : row)), | |||||
| ); | |||||
| } | |||||
| }; | }; | ||||
| const handleReleaseClick = (item: T) => { | |||||
| if (onReleaseClick) { | |||||
| onReleaseClick(item) | |||||
| } | |||||
| } | |||||
| const handleInputChange = ( | const handleInputChange = ( | ||||
| id: number, | id: number, | ||||
| field: keyof T, | field: keyof T, | ||||
| value: string | number[], | |||||
| // value: string | number[], | |||||
| value: string | number, | |||||
| ) => { | ) => { | ||||
| setEditedItems((prev) => | |||||
| prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)), | |||||
| ); | |||||
| if (handleEditChange) { | |||||
| handleEditChange(id, field, value) | |||||
| } else { | |||||
| setEditedItems((prev) => | |||||
| prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)), | |||||
| ); | |||||
| } | |||||
| }; | }; | ||||
| const handleDeleteClick = (id: number) => { | const handleDeleteClick = (id: number) => { | ||||
| @@ -157,6 +190,14 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| setEditedItems((prev) => prev.filter((item) => item.id !== id)); | setEditedItems((prev) => prev.filter((item) => item.id !== id)); | ||||
| }; | }; | ||||
| const handleCancelClick = (id: number) => { | |||||
| if (onCancelClick) { | |||||
| onCancelClick(id) | |||||
| } | |||||
| setEditingRowId(null) | |||||
| } | |||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log("[debug] isEdit in table", isEdit); | console.log("[debug] isEdit in table", isEdit); | ||||
| //TODO: switch all record to not in edit mode and save the changes | //TODO: switch all record to not in edit mode and save the changes | ||||
| @@ -188,7 +229,15 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| <TableRow hover tabIndex={-1} key={row.id}> | <TableRow hover tabIndex={-1} key={row.id}> | ||||
| {isDetailedType(type) && ( | {isDetailedType(type) && ( | ||||
| <TableCell> | <TableCell> | ||||
| <IconButton disabled={!isEdit}> | |||||
| <IconButton | |||||
| color="primary" | |||||
| disabled={ | |||||
| // !(row as unknown as DetailedProdScheduleLineResult).bomMaterials.every(ele => (ele.availableQty ?? 0) >= (ele.demandQty ?? 0)) | |||||
| // || | |||||
| editingRowId === row.id | |||||
| || (row as unknown as DetailedProdScheduleLineResult).approved} | |||||
| onClick={() => handleReleaseClick(row)} | |||||
| > | |||||
| <PlayCircleOutlineIcon /> | <PlayCircleOutlineIcon /> | ||||
| </IconButton> | </IconButton> | ||||
| </TableCell> | </TableCell> | ||||
| @@ -208,7 +257,7 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| {isDetailedType(type) && isEditable && ( | {isDetailedType(type) && isEditable && ( | ||||
| <IconButton | <IconButton | ||||
| disabled={!isEdit} | disabled={!isEdit} | ||||
| onClick={() => setEditingRowId(null)} | |||||
| onClick={() => handleCancelClick(row.id as number)} | |||||
| > | > | ||||
| <CancelIcon /> | <CancelIcon /> | ||||
| </IconButton> | </IconButton> | ||||
| @@ -232,20 +281,20 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| <> | <> | ||||
| {isDetailedType(type) && isEditable && ( | {isDetailedType(type) && isEditable && ( | ||||
| <IconButton | <IconButton | ||||
| disabled={!isEdit} | |||||
| disabled={!isEdit || (row as unknown as DetailedProdScheduleLineResult).approved} | |||||
| onClick={() => handleEditClick(row.id as number)} | onClick={() => handleEditClick(row.id as number)} | ||||
| > | > | ||||
| <EditIcon /> | <EditIcon /> | ||||
| </IconButton> | </IconButton> | ||||
| )} | )} | ||||
| {isDetailedType(type) && isEditable && ( | |||||
| {/* {isDetailedType(type) && isEditable && ( | |||||
| <IconButton | <IconButton | ||||
| disabled={!isEdit} | disabled={!isEdit} | ||||
| onClick={() => handleDeleteClick(row.id as number)} | onClick={() => handleDeleteClick(row.id as number)} | ||||
| > | > | ||||
| <DeleteIcon /> | <DeleteIcon /> | ||||
| </IconButton> | </IconButton> | ||||
| )} | |||||
| )} */} | |||||
| {hasCollapse && ( | {hasCollapse && ( | ||||
| <IconButton | <IconButton | ||||
| aria-label="expand row" | aria-label="expand row" | ||||
| @@ -286,6 +335,29 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| } | } | ||||
| /> | /> | ||||
| ); | ); | ||||
| case "input-number": | |||||
| return ( | |||||
| <TextField | |||||
| type="number" | |||||
| hiddenLabel={true} | |||||
| fullWidth | |||||
| defaultValue={row[columnName] as string} | |||||
| onChange={(e) => { | |||||
| handleInputChange( | |||||
| row.id as number, | |||||
| columnName, | |||||
| e.target.value, | |||||
| ) | |||||
| }} | |||||
| // onChange={(e) => | |||||
| // handleInputChange( | |||||
| // row.id as number, | |||||
| // columnName, | |||||
| // e.target.value, | |||||
| // ) | |||||
| // } | |||||
| /> | |||||
| ); | |||||
| // case 'multi-select': | // case 'multi-select': | ||||
| // //TODO: May need update if use | // //TODO: May need update if use | ||||
| // return ( | // return ( | ||||
| @@ -297,7 +369,9 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| // /> | // /> | ||||
| // ); | // ); | ||||
| case "read-only": | case "read-only": | ||||
| return <span>{row[columnName] as string}</span>; | |||||
| return column.renderCell ? ( | |||||
| <div style={column.style}>{column.renderCell(row)}</div> | |||||
| ) : <span>{row[columnName] as string}</span>; | |||||
| default: | default: | ||||
| return null; // Handle any default case if needed | return null; // Handle any default case if needed | ||||
| } | } | ||||
| @@ -330,8 +404,8 @@ function ScheduleTable<T extends ResultWithId>({ | |||||
| <BomMaterialTable | <BomMaterialTable | ||||
| type={type} | type={type} | ||||
| bomMaterial={ | bomMaterial={ | ||||
| isDetailedType(type) ? (row as unknown as RoughProdScheduleLineResultByFg).bomMaterials | |||||
| : (row as unknown as DetailedProdScheduleLineResult).bomMaterials | |||||
| isDetailedType(type) ? (row as unknown as RoughProdScheduleLineResultByFg).bomMaterials | |||||
| : (row as unknown as DetailedProdScheduleLineResult).bomMaterials | |||||
| } | } | ||||
| /> | /> | ||||
| </TableCell> | </TableCell> | ||||
| @@ -82,5 +82,6 @@ | |||||
| "mat": "物料", | "mat": "物料", | ||||
| "Product Count(s)": "產品數量", | "Product Count(s)": "產品數量", | ||||
| "Schedule Period To": "排程期間至", | "Schedule Period To": "排程期間至", | ||||
| "Overall": "總計" | |||||
| "Overall": "總計", | |||||
| "Back": "返回" | |||||
| } | } | ||||