| @@ -28,6 +28,7 @@ export type ItemsResult = { | |||
| qcChecks: ItemQc[] | |||
| action?: any | |||
| } | |||
| export type Result = { | |||
| item: ItemsResult | |||
| qcChecks: ItemQc[] | |||
| @@ -19,8 +19,9 @@ import {Add, Check, Close, EditNote} from "@mui/icons-material"; | |||
| import {ItemQc, ItemsResult} from "@/app/api/settings/item"; | |||
| import { useGridApiRef } from "@mui/x-data-grid"; | |||
| import ProductDetails from "@/components/CreateItem/ProductDetails"; | |||
| import QcDetails from "@/components/CreateItem/QcDetails"; | |||
| import DetailInfoCard from "@/components/RoughScheduleDetail/DetailInfoCard"; | |||
| import ViewByFGDetails from "@/components/RoughScheduleDetail/ViewByFGDetails"; | |||
| import ViewByBomDetails from "@/components/RoughScheduleDetail/ViewByBomDetails"; | |||
| type Props = { | |||
| isEditMode: boolean; | |||
| @@ -169,10 +170,8 @@ const RoughScheduleDetailView: React.FC<Props> = ({ | |||
| {serverError} | |||
| </Typography> | |||
| )} | |||
| {tabIndex === 0 && <ProductDetails />} | |||
| {tabIndex === 1 && <QcDetails apiRef={apiRef} />} | |||
| {/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */} | |||
| {/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */} | |||
| {tabIndex === 0 && <ViewByFGDetails isEdit={isEdit} apiRef={apiRef}/>} | |||
| {tabIndex === 1 && <ViewByBomDetails isEdit={isEdit} apiRef={apiRef} isHideButton={true} />} | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| name="submit" | |||
| @@ -0,0 +1,150 @@ | |||
| "use client"; | |||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | |||
| import { | |||
| GridColDef, | |||
| GridRowModel, | |||
| GridRenderEditCellParams, | |||
| GridEditInputCell, | |||
| GridRowSelectionModel, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import {MutableRefObject, useCallback, useMemo, useState} from "react"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import {Box, Grid, Tooltip, Typography} from "@mui/material"; | |||
| import { ItemQc } from "@/app/api/settings/item"; | |||
| import { QcChecksInputs } from "@/app/api/settings/qcCheck/actions"; | |||
| import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||
| import { RiceBowl } from "@mui/icons-material"; | |||
| import EditableSearchResults, {Column} from "@/components/SearchResults/EditableSearchResults"; | |||
| type Props = { | |||
| apiRef: MutableRefObject<GridApiCommunity> | |||
| }; | |||
| type EntryError = | |||
| | { | |||
| [field in keyof QcChecksInputs]?: string; | |||
| } | |||
| | undefined; | |||
| export type FGRecord = { | |||
| id: string | number | |||
| code: string; | |||
| name: string; | |||
| inStockQty: number; | |||
| purchaseQty: number; | |||
| } | |||
| const ViewByBomDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| const { | |||
| t, | |||
| i18n: { language }, | |||
| } = useTranslation(); | |||
| const { | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| } = useFormContext<CreateItemInputs>(); | |||
| // const apiRef = useGridApiRef(); | |||
| const dayPeriod = [ | |||
| '2025-05-11', | |||
| '2025-05-12', | |||
| '2025-05-13', | |||
| '2025-05-14', | |||
| '2025-05-15', | |||
| '2025-05-16', | |||
| '2025-05-17', | |||
| ]; | |||
| const fakeRecords = useMemo<FGRecord[][]>( | |||
| () => [ | |||
| [ | |||
| { id: 1, code: "mt1", name: "material 1", inStockQty: 10, purchaseQty: 1 }, | |||
| { id: 2, code: "mt2", name: "material 2", inStockQty: 20, purchaseQty: 199 }, | |||
| ], | |||
| [ | |||
| { id: 3, code: "mt3", name: "material 3", inStockQty: 30, purchaseQty: 3 }, | |||
| { id: 4, code: "mt4", name: "material 4", inStockQty: 40, purchaseQty: 499 }, | |||
| ], | |||
| [ | |||
| { id: 5, code: "mt5", name: "material 5", inStockQty: 50, purchaseQty: 5 }, | |||
| { id: 6, code: "mt6", name: "material 6", inStockQty: 60, purchaseQty: 699 }, | |||
| ], | |||
| [ | |||
| { id: 7, code: "mt7", name: "material 7", inStockQty: 70, purchaseQty: 7 }, | |||
| { id: 8, code: "mt8", name: "material 8", inStockQty: 80, purchaseQty: 899 }, | |||
| ], | |||
| [ | |||
| { id: 9, code: "mt9", name: "material 9", inStockQty: 90, purchaseQty: 9 }, | |||
| { id: 10, code: "mt10", name: "material 10", inStockQty: 100, purchaseQty: 999 }, | |||
| ], | |||
| [ | |||
| { id: 11, code: "mt11", name: "material 11", inStockQty: 110, purchaseQty: 11 }, | |||
| { id: 12, code: "mt12", name: "material 12", inStockQty: 120, purchaseQty: 1299 }, | |||
| ], | |||
| [ | |||
| { id: 13, code: "mt13", name: "material 13", inStockQty: 130, purchaseQty: 13 }, | |||
| { id: 14, code: "mt14", name: "material 14", inStockQty: 140, purchaseQty: 1499 }, | |||
| ], | |||
| ], | |||
| [] | |||
| ); | |||
| const [pagingController, setPagingController] = useState({ | |||
| pageNum: 1, | |||
| pageSize: 10, | |||
| totalCount: 0, | |||
| }) | |||
| const columns = useMemo<Column<any>[]>( | |||
| () => [ | |||
| { | |||
| field: "name", | |||
| label: "name", | |||
| type: 'read-only', | |||
| }, | |||
| { | |||
| field: "code", | |||
| label: "code", | |||
| type: 'read-only', | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "inStockQty", | |||
| label: "In Stock Amount", | |||
| type: 'read-only', | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "purchaseQty", | |||
| label: "Purchase Qty", | |||
| type: 'read-only', | |||
| }, | |||
| ], | |||
| [] | |||
| ); | |||
| return ( | |||
| <Grid container spacing={2}> | |||
| {dayPeriod.map((date, index) => ( | |||
| <Grid item xs={12} key={index}> | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| {date} | |||
| </Typography> | |||
| <EditableSearchResults<FGRecord> | |||
| items={fakeRecords[index]} // Use the corresponding records for the day | |||
| columns={columns} | |||
| setPagingController={setPagingController} | |||
| pagingController={pagingController} | |||
| isAutoPaging={false} | |||
| isHideButton={true} | |||
| isEdit={isEdit} | |||
| /> | |||
| </Grid> | |||
| ))} | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default ViewByBomDetails; | |||
| @@ -0,0 +1,150 @@ | |||
| "use client"; | |||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | |||
| import { | |||
| GridColDef, | |||
| GridRowModel, | |||
| GridRenderEditCellParams, | |||
| GridEditInputCell, | |||
| GridRowSelectionModel, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import {MutableRefObject, useCallback, useMemo, useState} from "react"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import {Box, Grid, Tooltip, Typography} from "@mui/material"; | |||
| import { ItemQc } from "@/app/api/settings/item"; | |||
| import { QcChecksInputs } from "@/app/api/settings/qcCheck/actions"; | |||
| import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||
| import { RiceBowl } from "@mui/icons-material"; | |||
| import EditableSearchResults, {Column} from "@/components/SearchResults/EditableSearchResults"; | |||
| type Props = { | |||
| apiRef: MutableRefObject<GridApiCommunity> | |||
| }; | |||
| type EntryError = | |||
| | { | |||
| [field in keyof QcChecksInputs]?: string; | |||
| } | |||
| | undefined; | |||
| export type FGRecord = { | |||
| id: string | number | |||
| code: string; | |||
| name: string; | |||
| inStockQty: number; | |||
| productionQty: number; | |||
| } | |||
| const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| const { | |||
| t, | |||
| i18n: { language }, | |||
| } = useTranslation(); | |||
| const { | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| } = useFormContext<CreateItemInputs>(); | |||
| // const apiRef = useGridApiRef(); | |||
| const dayPeriod = [ | |||
| '2025-05-11', | |||
| '2025-05-12', | |||
| '2025-05-13', | |||
| '2025-05-14', | |||
| '2025-05-15', | |||
| '2025-05-16', | |||
| '2025-05-17', | |||
| ]; | |||
| const fakeRecords = useMemo<FGRecord[][]>( | |||
| () => [ | |||
| [ | |||
| { id: 1, code: "fg1", name: "finished good 1", inStockQty: 10, productionQty: 1 }, | |||
| { id: 2, code: "fg2", name: "finished good 2", inStockQty: 20, productionQty: 199 }, | |||
| ], | |||
| [ | |||
| { id: 3, code: "fg3", name: "finished good 3", inStockQty: 30, productionQty: 3 }, | |||
| { id: 4, code: "fg4", name: "finished good 4", inStockQty: 40, productionQty: 499 }, | |||
| ], | |||
| [ | |||
| { id: 5, code: "fg5", name: "finished good 5", inStockQty: 50, productionQty: 5 }, | |||
| { id: 6, code: "fg6", name: "finished good 6", inStockQty: 60, productionQty: 699 }, | |||
| ], | |||
| [ | |||
| { id: 7, code: "fg7", name: "finished good 7", inStockQty: 70, productionQty: 7 }, | |||
| { id: 8, code: "fg8", name: "finished good 8", inStockQty: 80, productionQty: 899 }, | |||
| ], | |||
| [ | |||
| { id: 9, code: "fg9", name: "finished good 9", inStockQty: 90, productionQty: 9 }, | |||
| { id: 10, code: "fg10", name: "finished good 10", inStockQty: 100, productionQty: 999 }, | |||
| ], | |||
| [ | |||
| { id: 11, code: "fg11", name: "finished good 11", inStockQty: 110, productionQty: 11 }, | |||
| { id: 12, code: "fg12", name: "finished good 12", inStockQty: 120, productionQty: 1299 }, | |||
| ], | |||
| [ | |||
| { id: 13, code: "fg13", name: "finished good 13", inStockQty: 130, productionQty: 13 }, | |||
| { id: 14, code: "fg14", name: "finished good 14", inStockQty: 140, productionQty: 1499 }, | |||
| ], | |||
| ], | |||
| [] | |||
| ); | |||
| const [pagingController, setPagingController] = useState({ | |||
| pageNum: 1, | |||
| pageSize: 10, | |||
| totalCount: 0, | |||
| }) | |||
| const columns = useMemo<Column<any>[]>( | |||
| () => [ | |||
| { | |||
| field: "name", | |||
| label: "name", | |||
| type: 'read-only', | |||
| }, | |||
| { | |||
| field: "code", | |||
| label: "code", | |||
| type: 'read-only', | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "inStockQty", | |||
| label: "In Stock Amount", | |||
| type: 'read-only', | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "productionQty", | |||
| label: "Production Qty", | |||
| type: 'input', | |||
| }, | |||
| ], | |||
| [] | |||
| ); | |||
| return ( | |||
| <Grid container spacing={2}> | |||
| {dayPeriod.map((date, index) => ( | |||
| <Grid item xs={12} key={index}> | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| {date} | |||
| </Typography> | |||
| <EditableSearchResults<FGRecord> | |||
| items={fakeRecords[index]} // Use the corresponding records for the day | |||
| columns={columns} | |||
| setPagingController={setPagingController} | |||
| pagingController={pagingController} | |||
| isAutoPaging={false} | |||
| isHideButton={false} | |||
| isEdit={isEdit} | |||
| /> | |||
| </Grid> | |||
| ))} | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default ViewByFGDetails; | |||
| @@ -99,12 +99,12 @@ const RSSOverview: React.FC<Props> = ({ items }) => { | |||
| // buttonIcon: <EditNote />, | |||
| // }, | |||
| { | |||
| name: "fgName", | |||
| field: "fgName", | |||
| label: "Finished Goods Name", | |||
| type: 'input', | |||
| }, | |||
| { | |||
| name: "excludeDate", | |||
| field: "excludeDate", | |||
| label: t("Exclude Date"), | |||
| type: 'multi-select', | |||
| options: dayOptions, | |||
| @@ -22,11 +22,11 @@ export interface ResultWithId { | |||
| } | |||
| interface BaseColumn<T extends ResultWithId> { | |||
| name: keyof T; | |||
| field: keyof T; | |||
| label: string; | |||
| type: string; | |||
| options: T[]; | |||
| renderCell? : (T) => void; | |||
| options?: T[]; | |||
| renderCell?: (T) => void; | |||
| } | |||
| interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | |||
| @@ -55,6 +55,8 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| pagingController, | |||
| setPagingController, | |||
| isAutoPaging = true, | |||
| isEdit = true, | |||
| isHideButton = false, | |||
| }: Props<T>) { | |||
| const [page, setPage] = useState(0); | |||
| const [rowsPerPage, setRowsPerPage] = useState(10); | |||
| @@ -100,15 +102,29 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| setEditedItems((prev) => prev.filter(item => item.id !== id)); | |||
| }; | |||
| useEffect(()=>{ | |||
| console.log("[debug] isEdit in table", isEdit) | |||
| //TODO: switch all record to not in edit mode and save the changes | |||
| if (!isEdit) { | |||
| editedItems.forEach(item => { | |||
| // Call save logic here | |||
| console.log("Saving item:", item); | |||
| // Reset editing state if needed | |||
| }); | |||
| setEditingRowId(null); | |||
| } | |||
| },[isEdit]) | |||
| const table = ( | |||
| <> | |||
| <TableContainer sx={{ maxHeight: 440 }}> | |||
| <Table stickyHeader> | |||
| <TableHead> | |||
| <TableRow> | |||
| <TableCell>Actions</TableCell> {/* Action Column Header */} | |||
| {!isHideButton && <TableCell>Actions</TableCell>} {/* Action Column Header */} | |||
| {columns.map((column, idx) => ( | |||
| <TableCell key={`${column.name.toString()}${idx}`}> | |||
| <TableCell key={`${column.field.toString()}${idx}`}> | |||
| {column.label} | |||
| </TableCell> | |||
| ))} | |||
| @@ -117,29 +133,33 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| <TableBody> | |||
| {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( | |||
| <TableRow hover tabIndex={-1} key={item.id}> | |||
| <TableCell> | |||
| {editingRowId === item.id ? ( | |||
| <> | |||
| <IconButton onClick={() => handleSaveClick(item)}> | |||
| <SaveIcon /> | |||
| </IconButton> | |||
| <IconButton onClick={() => setEditingRowId(null)}> | |||
| <CancelIcon /> | |||
| </IconButton> | |||
| </> | |||
| ) : ( | |||
| <> | |||
| <IconButton onClick={() => handleEditClick(item.id as number)}> | |||
| <EditIcon /> | |||
| </IconButton> | |||
| <IconButton onClick={() => handleDeleteClick(item.id as number)}> | |||
| <DeleteIcon /> | |||
| </IconButton> | |||
| </> | |||
| )} | |||
| </TableCell> | |||
| { | |||
| !isHideButton && <TableCell> | |||
| {(editingRowId === item.id) ? ( | |||
| <> | |||
| <IconButton disabled={!isEdit} onClick={() => handleSaveClick(item)}> | |||
| <SaveIcon/> | |||
| </IconButton> | |||
| <IconButton disabled={!isEdit} onClick={() => setEditingRowId(null)}> | |||
| <CancelIcon/> | |||
| </IconButton> | |||
| </> | |||
| ) : ( | |||
| <> | |||
| <IconButton disabled={!isEdit} | |||
| onClick={() => handleEditClick(item.id as number)}> | |||
| <EditIcon/> | |||
| </IconButton> | |||
| <IconButton disabled={!isEdit} | |||
| onClick={() => handleDeleteClick(item.id as number)}> | |||
| <DeleteIcon/> | |||
| </IconButton> | |||
| </> | |||
| )} | |||
| </TableCell> | |||
| } | |||
| {columns.map((column, idx) => { | |||
| const columnName = column.name; | |||
| const columnName = column.field; | |||
| return ( | |||
| <TableCell key={`${columnName.toString()}-${idx}`}> | |||
| @@ -164,6 +184,12 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| onChange={(selectedValues) => handleInputChange(item.id as number, columnName, selectedValues)} | |||
| /> | |||
| ); | |||
| case 'read-only': | |||
| return ( | |||
| <span> | |||
| {item[columnName] as string} | |||
| </span> | |||
| ); | |||
| default: | |||
| return null; // Handle any default case if needed | |||
| } | |||
| @@ -172,7 +198,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| column.renderCell ? | |||
| column.renderCell(item) | |||
| : | |||
| <span onDoubleClick={() => handleEditClick(item.id as number)}> | |||
| <span onDoubleClick={() => isEdit && handleEditClick(item.id as number)}> | |||
| {item[columnName] as string} | |||
| </span> | |||
| )} | |||