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