@@ -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> | ||||
)} | )} | ||||