Procházet zdrojové kódy

[rough scheduling] update search and detail page

create_edit_user
jason.lam před 3 měsíci
rodič
revize
519bd4f5eb
6 změnil soubory, kde provedl 361 přidání a 35 odebrání
  1. +1
    -0
      src/app/api/settings/item/index.ts
  2. +4
    -5
      src/components/RoughScheduleDetail/RoughScheudleDetailView.tsx
  3. +150
    -0
      src/components/RoughScheduleDetail/ViewByBomDetails.tsx
  4. +150
    -0
      src/components/RoughScheduleDetail/ViewByFGDetails.tsx
  5. +2
    -2
      src/components/RoughScheduleSetting/RoughScheduleSetting.tsx
  6. +54
    -28
      src/components/SearchResults/EditableSearchResults.tsx

+ 1
- 0
src/app/api/settings/item/index.ts Zobrazit soubor

@@ -28,6 +28,7 @@ export type ItemsResult = {
qcChecks: ItemQc[]
action?: any
}

export type Result = {
item: ItemsResult
qcChecks: ItemQc[]


+ 4
- 5
src/components/RoughScheduleDetail/RoughScheudleDetailView.tsx Zobrazit soubor

@@ -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"


+ 150
- 0
src/components/RoughScheduleDetail/ViewByBomDetails.tsx Zobrazit soubor

@@ -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;

+ 150
- 0
src/components/RoughScheduleDetail/ViewByFGDetails.tsx Zobrazit soubor

@@ -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;

+ 2
- 2
src/components/RoughScheduleSetting/RoughScheduleSetting.tsx Zobrazit soubor

@@ -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,


+ 54
- 28
src/components/SearchResults/EditableSearchResults.tsx Zobrazit soubor

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


Načítá se…
Zrušit
Uložit