| @@ -28,6 +28,7 @@ export type ItemsResult = { | |||||
| qcChecks: ItemQc[] | qcChecks: ItemQc[] | ||||
| action?: any | action?: any | ||||
| } | } | ||||
| export type Result = { | export type Result = { | ||||
| item: ItemsResult | item: ItemsResult | ||||
| qcChecks: ItemQc[] | qcChecks: ItemQc[] | ||||
| @@ -19,8 +19,9 @@ import {Add, Check, Close, EditNote} from "@mui/icons-material"; | |||||
| import {ItemQc, ItemsResult} from "@/app/api/settings/item"; | import {ItemQc, ItemsResult} from "@/app/api/settings/item"; | ||||
| import { useGridApiRef } from "@mui/x-data-grid"; | import { useGridApiRef } from "@mui/x-data-grid"; | ||||
| import ProductDetails from "@/components/CreateItem/ProductDetails"; | import ProductDetails from "@/components/CreateItem/ProductDetails"; | ||||
| import QcDetails from "@/components/CreateItem/QcDetails"; | |||||
| import DetailInfoCard from "@/components/RoughScheduleDetail/DetailInfoCard"; | import DetailInfoCard from "@/components/RoughScheduleDetail/DetailInfoCard"; | ||||
| import ViewByFGDetails from "@/components/RoughScheduleDetail/ViewByFGDetails"; | |||||
| import ViewByBomDetails from "@/components/RoughScheduleDetail/ViewByBomDetails"; | |||||
| type Props = { | type Props = { | ||||
| isEditMode: boolean; | isEditMode: boolean; | ||||
| @@ -169,10 +170,8 @@ const RoughScheduleDetailView: React.FC<Props> = ({ | |||||
| {serverError} | {serverError} | ||||
| </Typography> | </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}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| <Button | <Button | ||||
| name="submit" | 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 />, | // buttonIcon: <EditNote />, | ||||
| // }, | // }, | ||||
| { | { | ||||
| name: "fgName", | |||||
| field: "fgName", | |||||
| label: "Finished Goods Name", | label: "Finished Goods Name", | ||||
| type: 'input', | type: 'input', | ||||
| }, | }, | ||||
| { | { | ||||
| name: "excludeDate", | |||||
| field: "excludeDate", | |||||
| label: t("Exclude Date"), | label: t("Exclude Date"), | ||||
| type: 'multi-select', | type: 'multi-select', | ||||
| options: dayOptions, | options: dayOptions, | ||||
| @@ -22,11 +22,11 @@ export interface ResultWithId { | |||||
| } | } | ||||
| interface BaseColumn<T extends ResultWithId> { | interface BaseColumn<T extends ResultWithId> { | ||||
| name: keyof T; | |||||
| field: keyof T; | |||||
| label: string; | label: string; | ||||
| type: string; | type: string; | ||||
| options: T[]; | |||||
| renderCell? : (T) => void; | |||||
| options?: T[]; | |||||
| renderCell?: (T) => void; | |||||
| } | } | ||||
| interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | ||||
| @@ -55,6 +55,8 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| pagingController, | pagingController, | ||||
| setPagingController, | setPagingController, | ||||
| isAutoPaging = true, | isAutoPaging = true, | ||||
| isEdit = true, | |||||
| isHideButton = false, | |||||
| }: Props<T>) { | }: Props<T>) { | ||||
| const [page, setPage] = useState(0); | const [page, setPage] = useState(0); | ||||
| const [rowsPerPage, setRowsPerPage] = useState(10); | const [rowsPerPage, setRowsPerPage] = useState(10); | ||||
| @@ -100,15 +102,29 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| setEditedItems((prev) => prev.filter(item => item.id !== id)); | 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 = ( | const table = ( | ||||
| <> | <> | ||||
| <TableContainer sx={{ maxHeight: 440 }}> | <TableContainer sx={{ maxHeight: 440 }}> | ||||
| <Table stickyHeader> | <Table stickyHeader> | ||||
| <TableHead> | <TableHead> | ||||
| <TableRow> | <TableRow> | ||||
| <TableCell>Actions</TableCell> {/* Action Column Header */} | |||||
| {!isHideButton && <TableCell>Actions</TableCell>} {/* Action Column Header */} | |||||
| {columns.map((column, idx) => ( | {columns.map((column, idx) => ( | ||||
| <TableCell key={`${column.name.toString()}${idx}`}> | |||||
| <TableCell key={`${column.field.toString()}${idx}`}> | |||||
| {column.label} | {column.label} | ||||
| </TableCell> | </TableCell> | ||||
| ))} | ))} | ||||
| @@ -117,29 +133,33 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| <TableBody> | <TableBody> | ||||
| {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( | {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( | ||||
| <TableRow hover tabIndex={-1} key={item.id}> | <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) => { | {columns.map((column, idx) => { | ||||
| const columnName = column.name; | |||||
| const columnName = column.field; | |||||
| return ( | return ( | ||||
| <TableCell key={`${columnName.toString()}-${idx}`}> | <TableCell key={`${columnName.toString()}-${idx}`}> | ||||
| @@ -164,6 +184,12 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| onChange={(selectedValues) => handleInputChange(item.id as number, columnName, selectedValues)} | onChange={(selectedValues) => handleInputChange(item.id as number, columnName, selectedValues)} | ||||
| /> | /> | ||||
| ); | ); | ||||
| case 'read-only': | |||||
| return ( | |||||
| <span> | |||||
| {item[columnName] as string} | |||||
| </span> | |||||
| ); | |||||
| default: | default: | ||||
| return null; // Handle any default case if needed | return null; // Handle any default case if needed | ||||
| } | } | ||||
| @@ -172,7 +198,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| column.renderCell ? | column.renderCell ? | ||||
| column.renderCell(item) | column.renderCell(item) | ||||
| : | : | ||||
| <span onDoubleClick={() => handleEditClick(item.id as number)}> | |||||
| <span onDoubleClick={() => isEdit && handleEditClick(item.id as number)}> | |||||
| {item[columnName] as string} | {item[columnName] as string} | ||||
| </span> | </span> | ||||
| )} | )} | ||||