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