diff --git a/src/app/(main)/scheduling/rough/edit/page.tsx b/src/app/(main)/scheduling/rough/edit/page.tsx index 4ed4e5d..549a932 100644 --- a/src/app/(main)/scheduling/rough/edit/page.tsx +++ b/src/app/(main)/scheduling/rough/edit/page.tsx @@ -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 ( <> - - {t("Demand Forecast Detail")} - - {/* */} - - }> - - - -); + + + }> + + + + + ); }; export default roughSchedulingDetail; diff --git a/src/app/(main)/scheduling/rough/page.tsx b/src/app/(main)/scheduling/rough/page.tsx index 81f25c3..dbefd57 100644 --- a/src/app/(main)/scheduling/rough/page.tsx +++ b/src/app/(main)/scheduling/rough/page.tsx @@ -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")} */} - }> - - + + }> + + + ); }; diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 733cc01..f791121 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -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 }, diff --git a/src/components/QcItemSearch/QcItemSearch.tsx b/src/components/QcItemSearch/QcItemSearch.tsx index 29a1ed2..5f8ea29 100644 --- a/src/components/QcItemSearch/QcItemSearch.tsx +++ b/src/components/QcItemSearch/QcItemSearch.tsx @@ -20,7 +20,7 @@ interface Props { type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; -const qcItemSearch: React.FC = ({ qcItems }) => { +const QcItemSearch: React.FC = ({ qcItems }) => { const { t } = useTranslation("qcItems"); const router = useRouter(); const pathname = usePathname() @@ -118,4 +118,4 @@ const qcItemSearch: React.FC = ({ qcItems }) => { ) }; -export default qcItemSearch; +export default QcItemSearch; diff --git a/src/components/RoughScheduleDetail/DetailInfoCard.tsx b/src/components/RoughScheduleDetail/DetailInfoCard.tsx index 79d98e7..ec3848b 100644 --- a/src/components/RoughScheduleDetail/DetailInfoCard.tsx +++ b/src/components/RoughScheduleDetail/DetailInfoCard.tsx @@ -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 = ({ recordDetails, isEditing}) => { = ({ 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} /> diff --git a/src/components/RoughScheduleDetail/RoughScheudleDetailView.tsx b/src/components/RoughScheduleDetail/RoughScheudleDetailView.tsx index 73b834e..b989fd4 100644 --- a/src/components/RoughScheduleDetail/RoughScheudleDetailView.tsx +++ b/src/components/RoughScheduleDetail/RoughScheudleDetailView.tsx @@ -43,7 +43,7 @@ const RoughScheduleDetailView: React.FC = ({ 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" diff --git a/src/components/RoughScheduleDetail/ViewByBomDetails.tsx b/src/components/RoughScheduleDetail/ViewByBomDetails.tsx index d091eb8..87da605 100644 --- a/src/components/RoughScheduleDetail/ViewByBomDetails.tsx +++ b/src/components/RoughScheduleDetail/ViewByBomDetails.tsx @@ -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 @@ -57,7 +59,7 @@ const ViewByBomDetails: React.FC = ({ apiRef, isEdit }) => { const { t, i18n: { language }, - } = useTranslation(); + } = useTranslation("schedule"); const { formState: { errors, defaultValues, touchedFields }, @@ -316,7 +318,7 @@ const ViewByBomDetails: React.FC = ({ apiRef, isEdit }) => { () => [ { field: "name", - label: "name", + label: t("name"), type: 'read-only', }, { @@ -329,46 +331,100 @@ const ViewByBomDetails: React.FC = ({ 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 = ({ 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 + } }, ], [] diff --git a/src/components/RoughScheduleDetail/ViewByFGDetails.tsx b/src/components/RoughScheduleDetail/ViewByFGDetails.tsx index 9ae79f2..b7c2d47 100644 --- a/src/components/RoughScheduleDetail/ViewByFGDetails.tsx +++ b/src/components/RoughScheduleDetail/ViewByFGDetails.tsx @@ -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 @@ -460,11 +461,23 @@ const ViewByFGDetails: React.FC = ({ 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 = ({ 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 + } }, ], [] diff --git a/src/components/SearchResults/EditableSearchResults.tsx b/src/components/SearchResults/EditableSearchResults.tsx index 9aaa563..cd191a4 100644 --- a/src/components/SearchResults/EditableSearchResults.tsx +++ b/src/components/SearchResults/EditableSearchResults.tsx @@ -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 { type: string; options?: T[]; renderCell?: (T) => void; + style?: Partial & { [propName: string]: string }; } interface ColumnWithAction extends BaseColumn { @@ -47,31 +49,31 @@ interface Props { items: T[], columns: Column[], 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({ - index, - items, - columns, - noWrapper, - pagingController, - setPagingController, - isAutoPaging = true, - isEdit = false, - isEditable = true, - hasCollapse = false, - }: Props) { + index, + items, + columns, + noWrapper, + pagingController, + setPagingController, + isAutoPaging = true, + isEdit = false, + isEditable = true, + hasCollapse = false, +}: Props) { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const [editingRowId, setEditingRowId] = useState(null); const [editedItems, setEditedItems] = useState(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({ const handleChangeRowsPerPage = (event: React.ChangeEvent) => { 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({ 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({ setEditingRowId(null); } - },[isEdit]) + }, [isEdit]) function Row(props: { row: T }) { const { row } = props; @@ -128,128 +130,132 @@ function EditableSearchResults({ console.log(row) return ( <> - - { - (isEditable || hasCollapse) && - {(editingRowId === row.id) ? ( - <> - { - isEditable && handleSaveClick(row)}> - - - } - { - isEditable && setEditingRowId(null)}> - - - } - { - hasCollapse && setOpen(!open)} - > - {open ? : } - - } - - ) : ( - <> - { - isEditable && handleEditClick(row.id as number)}> - - - } - { - isEditable && handleDeleteClick(row.id as number)}> - - - } - { - hasCollapse && setOpen(!open)} - > - {open ? : } - - } - - )} - - } - {columns.map((column, idx) => { - console.log(column) - const columnName = column.field; - return ( - - {editingRowId === row.id ? ( - (() => { - switch (column.type) { - case 'input': - console.log(column.type) - return ( - handleInputChange(row.id as number, columnName, e.target.value)} - /> - ); - case 'multi-select': - return ( - handleInputChange(row.id as number, columnName, selectedValues)} - /> - ); - case 'read-only': - return ( - - {row[columnName] as string} - - ); - default: - return null; // Handle any default case if needed + + { + (isEditable || hasCollapse) && + {(editingRowId === row.id) ? ( + <> + { + isEditable && handleSaveClick(row)}> + + + } + { + isEditable && setEditingRowId(null)}> + + } - })() + { + hasCollapse && setOpen(!open)} + > + {open ? : } + + } + ) : ( - column.renderCell ? - column.renderCell(row) - : - isEdit && handleEditClick(row.id as number)}> - {row[columnName] as string} - + <> + { + isEditable && handleEditClick(row.id as number)}> + + + } + { + isEditable && handleDeleteClick(row.id as number)}> + + + } + { + hasCollapse && setOpen(!open)} + > + {open ? : } + + } + )} - ); - })} - - - { - hasCollapse && - - - - - - - - - - -
-
-
- } -
- + } + {columns.map((column, idx) => { + console.log(column) + const columnName = column.field; + return ( + + {editingRowId === row.id ? ( + (() => { + switch (column.type) { + case 'input': + console.log(column.type) + return ( + handleInputChange(row.id as number, columnName, e.target.value)} + /> + ); + case 'multi-select': + return ( + handleInputChange(row.id as number, columnName, selectedValues)} + /> + ); + case 'read-only': + return ( + + {row[columnName] as string} + + ); + default: + return null; // Handle any default case if needed + } + })() + ) : ( + column.renderCell ? +
+ {column.renderCell(row)} +
+ : +
+ isEdit && handleEditClick(row.id as number)}> + {row[columnName] as String} + +
+ )} +
+ ); + })} +
+ + { + hasCollapse && + + + + + + + + + + +
+
+
+ } +
+ ) } @@ -261,7 +267,7 @@ function EditableSearchResults({ {(isEditable || hasCollapse) && Actions} {/* Action Column Header */} {columns.map((column, idx) => ( - + {column.label} ))} diff --git a/src/components/SearchResults/TempInputGridForMockUp.tsx b/src/components/SearchResults/TempInputGridForMockUp.tsx index 2fe3b2e..5cd609c 100644 --- a/src/components/SearchResults/TempInputGridForMockUp.tsx +++ b/src/components/SearchResults/TempInputGridForMockUp.tsx @@ -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", diff --git a/src/i18n/en/schedule.json b/src/i18n/en/schedule.json new file mode 100644 index 0000000..75a38bf --- /dev/null +++ b/src/i18n/en/schedule.json @@ -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)" +} \ No newline at end of file diff --git a/src/i18n/zh/schedule.json b/src/i18n/zh/schedule.json new file mode 100644 index 0000000..75a38bf --- /dev/null +++ b/src/i18n/zh/schedule.json @@ -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)" +} \ No newline at end of file