| @@ -1,6 +1,6 @@ | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import RoughSchedule from "@/components/RoughSchedule"; | |||
| import { getServerI18n } from "@/i18n"; | |||
| import { getServerI18n, I18nProvider } from "@/i18n"; | |||
| import Add from "@mui/icons-material/Add"; | |||
| import Button from "@mui/material/Button"; | |||
| import Stack from "@mui/material/Stack"; | |||
| @@ -15,22 +15,22 @@ export const metadata: Metadata = { | |||
| }; | |||
| const roughSchedulingDetail: React.FC = async () => { | |||
| const project = TypeEnum.PRODUCT | |||
| const { t } = await getServerI18n(project); | |||
| // const project = TypeEnum.PRODUCT | |||
| const { t } = await getServerI18n("project"); | |||
| // preloadClaims(); | |||
| return ( | |||
| <> | |||
| <Stack | |||
| direction="row" | |||
| justifyContent="space-between" | |||
| flexWrap="wrap" | |||
| rowGap={2} | |||
| > | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Demand Forecast Detail")} | |||
| </Typography> | |||
| {/* <Button | |||
| justifyContent="space-between" | |||
| flexWrap="wrap" | |||
| rowGap={2} | |||
| > | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Demand Forecast Detail")} | |||
| </Typography> | |||
| {/* <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| @@ -38,12 +38,14 @@ const roughSchedulingDetail: React.FC = async () => { | |||
| > | |||
| {t("Create product")} | |||
| </Button> */} | |||
| </Stack> | |||
| <Suspense fallback={<RoughScheduleDetailView.Loading />}> | |||
| <RoughScheduleDetailView /> | |||
| </Suspense> | |||
| </> | |||
| ); | |||
| </Stack> | |||
| <I18nProvider namespaces={["schedule"]}> | |||
| <Suspense fallback={<RoughScheduleDetailView.Loading />}> | |||
| <RoughScheduleDetailView /> | |||
| </Suspense> | |||
| </I18nProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| export default roughSchedulingDetail; | |||
| @@ -1,6 +1,6 @@ | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import RoughSchedule from "@/components/RoughSchedule"; | |||
| import { getServerI18n } from "@/i18n"; | |||
| import { getServerI18n, I18nProvider } from "@/i18n"; | |||
| import Add from "@mui/icons-material/Add"; | |||
| import Button from "@mui/material/Button"; | |||
| import Stack from "@mui/material/Stack"; | |||
| @@ -14,8 +14,8 @@ export const metadata: Metadata = { | |||
| }; | |||
| const roughScheduling: React.FC = async () => { | |||
| const project = TypeEnum.PRODUCT | |||
| const { t } = await getServerI18n(project); | |||
| // const project = TypeEnum.PRODUCT | |||
| const { t } = await getServerI18n("schedule"); | |||
| // preloadClaims(); | |||
| return ( | |||
| @@ -38,9 +38,11 @@ const roughScheduling: React.FC = async () => { | |||
| {t("Create product")} | |||
| </Button> */} | |||
| </Stack> | |||
| <Suspense fallback={<RoughSchedule.Loading />}> | |||
| <RoughSchedule /> | |||
| </Suspense> | |||
| <I18nProvider namespaces={["schedule"]}> | |||
| <Suspense fallback={<RoughSchedule.Loading />}> | |||
| <RoughSchedule /> | |||
| </Suspense> | |||
| </I18nProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -8,6 +8,14 @@ export const moneyFormatter = new Intl.NumberFormat("en-HK", { | |||
| currency: "HKD", | |||
| }); | |||
| export const decimalFormatter = new Intl.NumberFormat("en-HK", { | |||
| minimumFractionDigits: 2, | |||
| }) | |||
| export const integerFormatter = new Intl.NumberFormat("en-HK", { | |||
| }) | |||
| export const stockInLineStatusMap: { [status: string]: {key: string, value: number} } = { | |||
| draft: { key: "draft", value: 0 }, | |||
| pending: { key: "pending", value: 1 }, | |||
| @@ -20,7 +20,7 @@ interface Props { | |||
| type SearchQuery = Partial<Omit<QcItemResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||
| const QcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||
| const { t } = useTranslation("qcItems"); | |||
| const router = useRouter(); | |||
| const pathname = usePathname() | |||
| @@ -118,4 +118,4 @@ const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||
| ) | |||
| }; | |||
| export default qcItemSearch; | |||
| export default QcItemSearch; | |||
| @@ -17,6 +17,7 @@ import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | |||
| import {NumberInputProps} from "@/components/CreateItem/NumberInputProps"; | |||
| import { integerFormatter } from "@/app/utils/formatUtil"; | |||
| type Props = { | |||
| // isEditMode: boolean; | |||
| @@ -68,7 +69,7 @@ const DetailInfoCard: React.FC<Props> = ({ recordDetails, isEditing}) => { | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Total FG Type")} | |||
| label={t("Total FG Item")} | |||
| fullWidth | |||
| {...register("productCount", { | |||
| required: "code required!", | |||
| @@ -87,7 +88,7 @@ const DetailInfoCard: React.FC<Props> = ({ recordDetails, isEditing}) => { | |||
| required: "type required!", | |||
| })} | |||
| disabled={!isEditing} | |||
| defaultValue={details?.productionCount} | |||
| defaultValue={typeof(details?.productionCount) == "number" ? integerFormatter.format(details?.productionCount) : details?.productionCount} | |||
| error={Boolean(errors.type)} | |||
| helperText={errors.type?.message} | |||
| /> | |||
| @@ -43,7 +43,7 @@ const RoughScheduleDetailView: React.FC<Props> = ({ | |||
| console.log(params.get("id")) | |||
| const [serverError, setServerError] = useState(""); | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const { t } = useTranslation(); | |||
| const { t } = useTranslation("schedule") | |||
| const router = useRouter(); | |||
| const [isEdit, setIsEdit] = useState(false); | |||
| //const title = "Demand Forecast Detail" | |||
| @@ -19,6 +19,8 @@ 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"; | |||
| import { decimalFormatter } from "@/app/utils/formatUtil"; | |||
| import { GridRenderCellParams } from "@mui/x-data-grid"; | |||
| type Props = { | |||
| apiRef: MutableRefObject<GridApiCommunity> | |||
| @@ -57,7 +59,7 @@ const ViewByBomDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| const { | |||
| t, | |||
| i18n: { language }, | |||
| } = useTranslation(); | |||
| } = useTranslation("schedule"); | |||
| const { | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| @@ -316,7 +318,7 @@ const ViewByBomDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| () => [ | |||
| { | |||
| field: "name", | |||
| label: "name", | |||
| label: t("name"), | |||
| type: 'read-only', | |||
| }, | |||
| { | |||
| @@ -329,46 +331,100 @@ const ViewByBomDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| field: "inStockQty", | |||
| label: "In Stock Amount", | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.inStockQty) == "number") { | |||
| return decimalFormatter.format(row.inStockQty) | |||
| } | |||
| return row.inStockQty | |||
| } | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "overallPurchaseQty", | |||
| label: "Total Purchase Qty", | |||
| label: t("Total Demand Qty"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.overallPurchaseQty) == "number") { | |||
| return decimalFormatter.format(row.overallPurchaseQty) | |||
| } | |||
| return row.overallPurchaseQty | |||
| } | |||
| }, | |||
| { | |||
| field: "purchaseQty1", | |||
| label: "Purchase Qty (Day1)", | |||
| label: t("Demand Qty (Day1)"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.purchaseQty1) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty1) | |||
| } | |||
| return row.purchaseQty1 | |||
| } | |||
| }, | |||
| { | |||
| field: "purchaseQty2", | |||
| label: "Purchase Qty (Day2)", | |||
| label: t("Demand Qty (Day2)"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.purchaseQty2) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty2) | |||
| } | |||
| return row.purchaseQty2 | |||
| } | |||
| }, | |||
| { | |||
| field: "purchaseQty3", | |||
| label: "Purchase Qty (Day3)", | |||
| label: t("Demand Qty (Day3)"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.purchaseQty3) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty3) | |||
| } | |||
| return row.purchaseQty3 | |||
| } | |||
| }, | |||
| { | |||
| field: "purchaseQty4", | |||
| label: "Purchase Qty (Day4)", | |||
| label: t("Demand Qty (Day4)"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.purchaseQty4) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty4) | |||
| } | |||
| return row.purchaseQty4 | |||
| } | |||
| },{ | |||
| field: "purchaseQty5", | |||
| label: "Purchase Qty (Day5)", | |||
| label: t("Demand Qty (Day5)"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.purchaseQty5) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty5) | |||
| } | |||
| return row.purchaseQty5 | |||
| } | |||
| }, | |||
| { | |||
| field: "purchaseQty6", | |||
| label: "Purchase Qty (Day6)", | |||
| label: t("Demand Qty (Day6)"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.purchaseQty6) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty6) | |||
| } | |||
| return row.purchaseQty6 | |||
| } | |||
| }, | |||
| { | |||
| field: "purchaseQty7", | |||
| label: "Purchase Qty (Day7)", | |||
| label: t("Demand Qty (Day7)"), | |||
| type: 'read-only', | |||
| renderCell: (row: FGOverallRecord) => { | |||
| if (typeof(row.purchaseQty7) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty7) | |||
| } | |||
| return row.purchaseQty7 | |||
| } | |||
| }, | |||
| ], | |||
| [] | |||
| @@ -392,11 +448,23 @@ const ViewByBomDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| label: "In Stock Amount", | |||
| type: 'read-only', | |||
| // editable: true, | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.inStockQty) == "number") { | |||
| return decimalFormatter.format(row.inStockQty) | |||
| } | |||
| return row.inStockQty | |||
| } | |||
| }, | |||
| { | |||
| field: "purchaseQty", | |||
| label: "Purchase Qty", | |||
| type: 'read-only', | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.purchaseQty) == "number") { | |||
| return decimalFormatter.format(row.purchaseQty) | |||
| } | |||
| return row.purchaseQty | |||
| } | |||
| }, | |||
| ], | |||
| [] | |||
| @@ -19,6 +19,7 @@ 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"; | |||
| import { decimalFormatter } from "@/app/utils/formatUtil"; | |||
| type Props = { | |||
| apiRef: MutableRefObject<GridApiCommunity> | |||
| @@ -460,11 +461,23 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| label: "In Stock Amount", | |||
| type: 'read-only', | |||
| // editable: true, | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.inStockQty) == "number") { | |||
| return decimalFormatter.format(row.inStockQty) | |||
| } | |||
| return row.inStockQty | |||
| } | |||
| }, | |||
| { | |||
| field: "productionQty", | |||
| label: "Production Qty", | |||
| type: 'input', | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.productionQty) == "number") { | |||
| return decimalFormatter.format(row.productionQty ?? 0) | |||
| } | |||
| return row.productionQty | |||
| } | |||
| }, | |||
| ], | |||
| [] | |||
| @@ -487,24 +500,60 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||
| field: "lastMonthAvgStock", | |||
| label: "Last Month Average Stock", | |||
| type: 'read-only', | |||
| style: { | |||
| textAlign: "center", | |||
| }, | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.lastMonthAvgStock) == "number") { | |||
| return decimalFormatter.format(row.lastMonthAvgStock) | |||
| } | |||
| return row.lastMonthAvgStock | |||
| } | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "safetyStock", | |||
| label: "Safety Stock", | |||
| type: 'read-only', | |||
| type: 'read-only-int', | |||
| style: { | |||
| textAlign: "center", | |||
| }, | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.safetyStock) == "number") { | |||
| return decimalFormatter.format(row.safetyStock) | |||
| } | |||
| return row.safetyStock | |||
| } | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "inStockQty", | |||
| label: "In Stock Amount", | |||
| type: 'read-only', | |||
| type: 'read-only-int', | |||
| style: { | |||
| textAlign: "center", | |||
| }, | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.inStockQty) == "number") { | |||
| return decimalFormatter.format(row.inStockQty) | |||
| } | |||
| return row.inStockQty | |||
| } | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "productionQty", | |||
| label: "Production Qty", | |||
| type: 'input', | |||
| type: 'read-only-int', | |||
| style: { | |||
| textAlign: "right", | |||
| }, | |||
| renderCell: (row: FGRecord) => { | |||
| if (typeof(row.productionQty) == "number") { | |||
| return decimalFormatter.format(row.productionQty) | |||
| } | |||
| return row.productionQty | |||
| } | |||
| }, | |||
| ], | |||
| [] | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import React, {useEffect, useState} from "react"; | |||
| import React, { useEffect, useState } from "react"; | |||
| import Paper from "@mui/material/Paper"; | |||
| import Table from "@mui/material/Table"; | |||
| import TableBody from "@mui/material/TableBody"; | |||
| @@ -20,6 +20,7 @@ import { Collapse } from "@mui/material"; | |||
| import TempInputGridForMockUp from "./TempInputGridForMockUp"; | |||
| import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | |||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | |||
| import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | |||
| export interface ResultWithId { | |||
| id: string | number; | |||
| @@ -31,6 +32,7 @@ interface BaseColumn<T extends ResultWithId> { | |||
| type: string; | |||
| options?: T[]; | |||
| renderCell?: (T) => void; | |||
| style?: Partial<HTMLElement["style"]> & { [propName: string]: string }; | |||
| } | |||
| interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | |||
| @@ -47,31 +49,31 @@ interface Props<T extends ResultWithId> { | |||
| items: T[], | |||
| columns: Column<T>[], | |||
| noWrapper?: boolean, | |||
| setPagingController: (value: { pageNum: number; pageSize: number; totalCount: number, index?: number}) => void, | |||
| setPagingController: (value: { pageNum: number; pageSize: number; totalCount: number, index?: number }) => void, | |||
| pagingController: { pageNum: number; pageSize: number; totalCount: number }, | |||
| isAutoPaging: boolean | |||
| } | |||
| function EditableSearchResults<T extends ResultWithId>({ | |||
| index, | |||
| items, | |||
| columns, | |||
| noWrapper, | |||
| pagingController, | |||
| setPagingController, | |||
| isAutoPaging = true, | |||
| isEdit = false, | |||
| isEditable = true, | |||
| hasCollapse = false, | |||
| }: Props<T>) { | |||
| index, | |||
| items, | |||
| columns, | |||
| noWrapper, | |||
| pagingController, | |||
| setPagingController, | |||
| isAutoPaging = true, | |||
| isEdit = false, | |||
| isEditable = true, | |||
| hasCollapse = false, | |||
| }: Props<T>) { | |||
| const [page, setPage] = useState(0); | |||
| const [rowsPerPage, setRowsPerPage] = useState(10); | |||
| const [editingRowId, setEditingRowId] = useState<number | null>(null); | |||
| const [editedItems, setEditedItems] = useState<T[]>(items); | |||
| console.log(items) | |||
| useEffect(()=>{ | |||
| console.log(items) | |||
| useEffect(() => { | |||
| setEditedItems(items) | |||
| },[items]) | |||
| }, [items]) | |||
| const handleChangePage = (_event: unknown, newPage: number) => { | |||
| setPage(newPage); | |||
| setPagingController({ ...pagingController, pageNum: newPage + 1 }, (index ?? -1)); | |||
| @@ -80,7 +82,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
| setRowsPerPage(+event.target.value); | |||
| setPage(0); | |||
| setPagingController({ ...pagingController, pageSize: +event.target.value, pageNum: 1 , index: index}); | |||
| setPagingController({ ...pagingController, pageSize: +event.target.value, pageNum: 1, index: index }); | |||
| }; | |||
| const handleEditClick = (id: number) => { | |||
| @@ -108,7 +110,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| setEditedItems((prev) => prev.filter(item => item.id !== id)); | |||
| }; | |||
| useEffect(()=>{ | |||
| useEffect(() => { | |||
| console.log("[debug] isEdit in table", isEdit) | |||
| //TODO: switch all record to not in edit mode and save the changes | |||
| if (!isEdit) { | |||
| @@ -120,7 +122,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| setEditingRowId(null); | |||
| } | |||
| },[isEdit]) | |||
| }, [isEdit]) | |||
| function Row(props: { row: T }) { | |||
| const { row } = props; | |||
| @@ -128,128 +130,132 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| console.log(row) | |||
| return ( | |||
| <> | |||
| <TableRow hover tabIndex={-1} key={row.id}> | |||
| { | |||
| (isEditable || hasCollapse) && <TableCell> | |||
| {(editingRowId === row.id) ? ( | |||
| <> | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} onClick={() => handleSaveClick(row)}> | |||
| <SaveIcon/> | |||
| </IconButton> | |||
| } | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} onClick={() => setEditingRowId(null)}> | |||
| <CancelIcon/> | |||
| </IconButton> | |||
| } | |||
| { | |||
| hasCollapse && <IconButton | |||
| aria-label="expand row" | |||
| size="small" | |||
| onClick={() => setOpen(!open)} | |||
| > | |||
| {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} | |||
| </IconButton> | |||
| } | |||
| </> | |||
| ) : ( | |||
| <> | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} | |||
| onClick={() => handleEditClick(row.id as number)}> | |||
| <EditIcon/> | |||
| </IconButton> | |||
| } | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} | |||
| onClick={() => handleDeleteClick(row.id as number)}> | |||
| <DeleteIcon/> | |||
| </IconButton> | |||
| } | |||
| { | |||
| hasCollapse && <IconButton | |||
| aria-label="expand row" | |||
| size="small" | |||
| onClick={() => setOpen(!open)} | |||
| > | |||
| {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} | |||
| </IconButton> | |||
| } | |||
| </> | |||
| )} | |||
| </TableCell> | |||
| } | |||
| {columns.map((column, idx) => { | |||
| console.log(column) | |||
| const columnName = column.field; | |||
| return ( | |||
| <TableCell key={`${columnName.toString()}-${idx}`}> | |||
| {editingRowId === row.id ? ( | |||
| (() => { | |||
| switch (column.type) { | |||
| case 'input': | |||
| console.log(column.type) | |||
| return ( | |||
| <TextField | |||
| hiddenLabel={true} | |||
| fullWidth | |||
| defaultValue={row[columnName] as string} | |||
| onChange={(e) => handleInputChange(row.id as number, columnName, e.target.value)} | |||
| /> | |||
| ); | |||
| case 'multi-select': | |||
| return ( | |||
| <MultiSelect | |||
| //label={column.label} | |||
| options={column.options} | |||
| selectedValues={[]} | |||
| onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)} | |||
| /> | |||
| ); | |||
| case 'read-only': | |||
| return ( | |||
| <span> | |||
| {row[columnName] as string} | |||
| </span> | |||
| ); | |||
| default: | |||
| return null; // Handle any default case if needed | |||
| <TableRow hover tabIndex={-1} key={row.id}> | |||
| { | |||
| (isEditable || hasCollapse) && <TableCell> | |||
| {(editingRowId === row.id) ? ( | |||
| <> | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} onClick={() => handleSaveClick(row)}> | |||
| <SaveIcon /> | |||
| </IconButton> | |||
| } | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} onClick={() => setEditingRowId(null)}> | |||
| <CancelIcon /> | |||
| </IconButton> | |||
| } | |||
| })() | |||
| { | |||
| hasCollapse && <IconButton | |||
| aria-label="expand row" | |||
| size="small" | |||
| onClick={() => setOpen(!open)} | |||
| > | |||
| {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} | |||
| </IconButton> | |||
| } | |||
| </> | |||
| ) : ( | |||
| column.renderCell ? | |||
| column.renderCell(row) | |||
| : | |||
| <span onDoubleClick={() => isEdit && handleEditClick(row.id as number)}> | |||
| {row[columnName] as string} | |||
| </span> | |||
| <> | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} | |||
| onClick={() => handleEditClick(row.id as number)}> | |||
| <EditIcon /> | |||
| </IconButton> | |||
| } | |||
| { | |||
| isEditable && <IconButton disabled={!isEdit} | |||
| onClick={() => handleDeleteClick(row.id as number)}> | |||
| <DeleteIcon /> | |||
| </IconButton> | |||
| } | |||
| { | |||
| hasCollapse && <IconButton | |||
| aria-label="expand row" | |||
| size="small" | |||
| onClick={() => setOpen(!open)} | |||
| > | |||
| {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} | |||
| </IconButton> | |||
| } | |||
| </> | |||
| )} | |||
| </TableCell> | |||
| ); | |||
| })} | |||
| </TableRow> | |||
| <TableRow> | |||
| { | |||
| hasCollapse && | |||
| <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> | |||
| <Collapse in={open} timeout="auto" unmountOnExit> | |||
| <Table> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell> | |||
| <TempInputGridForMockUp | |||
| stockInLine={row.lines as any[]} | |||
| /> | |||
| </TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| </Collapse> | |||
| </TableCell> | |||
| } | |||
| </TableRow> | |||
| </> | |||
| } | |||
| {columns.map((column, idx) => { | |||
| console.log(column) | |||
| const columnName = column.field; | |||
| return ( | |||
| <TableCell key={`${columnName.toString()}-${idx}`}> | |||
| {editingRowId === row.id ? ( | |||
| (() => { | |||
| switch (column.type) { | |||
| case 'input': | |||
| console.log(column.type) | |||
| return ( | |||
| <TextField | |||
| hiddenLabel={true} | |||
| fullWidth | |||
| defaultValue={row[columnName] as string} | |||
| onChange={(e) => handleInputChange(row.id as number, columnName, e.target.value)} | |||
| /> | |||
| ); | |||
| case 'multi-select': | |||
| return ( | |||
| <MultiSelect | |||
| //label={column.label} | |||
| options={column.options} | |||
| selectedValues={[]} | |||
| onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)} | |||
| /> | |||
| ); | |||
| case 'read-only': | |||
| return ( | |||
| <span> | |||
| {row[columnName] as string} | |||
| </span> | |||
| ); | |||
| default: | |||
| return null; // Handle any default case if needed | |||
| } | |||
| })() | |||
| ) : ( | |||
| column.renderCell ? | |||
| <div style={column.style}> | |||
| {column.renderCell(row)} | |||
| </div> | |||
| : | |||
| <div style={column.style}> | |||
| <span onDoubleClick={() => isEdit && handleEditClick(row.id as number)}> | |||
| {row[columnName] as String} | |||
| </span> | |||
| </div> | |||
| )} | |||
| </TableCell> | |||
| ); | |||
| })} | |||
| </TableRow> | |||
| <TableRow> | |||
| { | |||
| hasCollapse && | |||
| <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> | |||
| <Collapse in={open} timeout="auto" unmountOnExit> | |||
| <Table> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell> | |||
| <TempInputGridForMockUp | |||
| stockInLine={row.lines as any[]} | |||
| /> | |||
| </TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| </Collapse> | |||
| </TableCell> | |||
| } | |||
| </TableRow> | |||
| </> | |||
| ) | |||
| } | |||
| @@ -261,7 +267,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||
| <TableRow> | |||
| {(isEditable || hasCollapse) && <TableCell>Actions</TableCell>} {/* Action Column Header */} | |||
| {columns.map((column, idx) => ( | |||
| <TableCell key={`${column.field.toString()}${idx}`}> | |||
| <TableCell style={column.style} key={`${column.field.toString()}${idx}`}> | |||
| {column.label} | |||
| </TableCell> | |||
| ))} | |||
| @@ -35,7 +35,7 @@ import { QcItemWithChecks } from "src/app/api/qc"; | |||
| import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | |||
| import { createStockInLine, testFetch } from "@/app/api/po/actions"; | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { decimalFormatter, stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| interface ResultWithId { | |||
| id: number; | |||
| @@ -180,13 +180,19 @@ function TempInputGridForMockUp({ stockInLine }: Props) { | |||
| flex: 0.5, | |||
| type: "number", | |||
| editable: true, | |||
| renderCell: (row) => { | |||
| return decimalFormatter.format(row.value) | |||
| } | |||
| // replace with tooltip + content | |||
| }, | |||
| { | |||
| field: "purchaseQty", | |||
| headerName: "purchaseQty", | |||
| flex: 0.5, | |||
| editable: true | |||
| editable: true, | |||
| renderCell: (row) => { | |||
| return decimalFormatter.format(row.value) | |||
| } | |||
| }, | |||
| // { | |||
| // field: "actions", | |||
| @@ -0,0 +1,10 @@ | |||
| { | |||
| "Total Demand Qty": "Total Demand Qty", | |||
| "Demand Qty (Day1)": "Demand Qty (Day1)", | |||
| "Demand Qty (Day2)": "Demand Qty (Day2)", | |||
| "Demand Qty (Day3)": "Demand Qty (Day3)", | |||
| "Demand Qty (Day4)": "Demand Qty (Day4)", | |||
| "Demand Qty (Day5)": "Demand Qty (Day5)", | |||
| "Demand Qty (Day6)": "Demand Qty (Day6)", | |||
| "Demand Qty (Day7)": "Demand Qty (Day7)" | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| { | |||
| "Total Demand Qty": "Total Demand Qty", | |||
| "Demand Qty (Day1)": "Demand Qty (Day1)", | |||
| "Demand Qty (Day2)": "Demand Qty (Day2)", | |||
| "Demand Qty (Day3)": "Demand Qty (Day3)", | |||
| "Demand Qty (Day4)": "Demand Qty (Day4)", | |||
| "Demand Qty (Day5)": "Demand Qty (Day5)", | |||
| "Demand Qty (Day6)": "Demand Qty (Day6)", | |||
| "Demand Qty (Day7)": "Demand Qty (Day7)" | |||
| } | |||