MSI\2Fi 1 mês atrás
pai
commit
ffe414c1d0
26 arquivos alterados com 774 adições e 317 exclusões
  1. +1
    -1
      package.json
  2. +39
    -1
      src/app/api/inventory/actions.ts
  3. +36
    -1
      src/app/api/inventory/index.ts
  4. +4
    -0
      src/app/api/po/actions.ts
  5. +7
    -0
      src/app/utils/commonUtil.ts
  6. +2
    -1
      src/app/utils/formatUtil.ts
  7. +14
    -8
      src/components/InputDataGrid/InputDataGrid.tsx
  8. +110
    -0
      src/components/InventorySearch/InventoryLotLineTable.tsx
  9. +147
    -99
      src/components/InventorySearch/InventorySearch.tsx
  10. +111
    -0
      src/components/InventorySearch/InventoryTable.tsx
  11. +13
    -12
      src/components/PickOrderDetail/PickOrderDetail.tsx
  12. +2
    -1
      src/components/PickOrderSearch/ConsolidatedPickOrders.tsx
  13. +39
    -36
      src/components/PickOrderSearch/CreateForm.tsx
  14. +2
    -2
      src/components/PickOrderSearch/CreatePickOrderModal.tsx
  15. +31
    -1
      src/components/PoDetail/EscalationForm.tsx
  16. +10
    -35
      src/components/PoDetail/PoDetail.tsx
  17. +80
    -72
      src/components/PoDetail/PoInputGrid.tsx
  18. +31
    -1
      src/components/PoDetail/PoQcStockInModal.tsx
  19. +1
    -1
      src/components/PoDetail/QcForm.tsx
  20. +2
    -2
      src/components/PoDetail/QcSelect.tsx
  21. +30
    -5
      src/components/PoDetail/StockInForm.tsx
  22. +1
    -1
      src/components/SearchBox/SearchBox.tsx
  23. +49
    -34
      src/components/SearchResults/SearchResults.tsx
  24. +1
    -0
      src/i18n/zh/common.json
  25. +10
    -2
      src/i18n/zh/inventory.json
  26. +1
    -1
      src/i18n/zh/purchaseOrder.json

+ 1
- 1
package.json Ver arquivo

@@ -5,7 +5,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "NODE_OPTIONS='--inspect' next start",
"start": "set NODE_OPTIONS=--inspect&& next start",
"lint": "next lint",
"type-check": "tsc --noEmit"
},


+ 39
- 1
src/app/api/inventory/actions.ts Ver arquivo

@@ -3,9 +3,11 @@ import { BASE_API_URL } from "@/config/api";
// import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { revalidateTag } from "next/cache";
import { cache } from "react";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil";
import { QcItemResult } from "../settings/qcItem";
import { RecordsRes } from "../utils";
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
import { InventoryLotLineResult, InventoryResult } from ".";
// import { BASE_API_URL } from "@/config/api";

export interface LotLineInfo {
@@ -18,6 +20,26 @@ export interface LotLineInfo {
uom: string;
}

export interface SearchInventoryLotLine extends Pageable {
itemId: number;
}

export interface SearchInventory extends Pageable {
code: string;
name: string;
type: string;
}

export interface InventoryResultByPage {
total: number;
records: InventoryResult[];
}

export interface InventoryLotLineResultByPage {
total: number;
records: InventoryLotLineResult[];
}

export const fetchLotDetail = cache(async (stockInLineId: number) => {
return serverFetchJson<LotLineInfo>(
`${BASE_API_URL}/inventoryLotLine/lot-detail/${stockInLineId}`,
@@ -27,3 +49,19 @@ export const fetchLotDetail = cache(async (stockInLineId: number) => {
},
);
});

export const fetchInventories = cache(async (data: SearchInventory) => {
const queryStr = convertObjToURLSearchParams(data)

return serverFetchJson<InventoryResultByPage>(`${BASE_API_URL}/inventory/getRecordByPage?${queryStr}`,
{ next: { tags: ["inventories"] } }
)
})


export const fetchInventoryLotLines = cache(async (data: SearchInventoryLotLine) => {
const queryStr = convertObjToURLSearchParams(data)
return serverFetchJson<InventoryLotLineResultByPage>(`${BASE_API_URL}/inventoryLotLine/getRecordByPage?${queryStr}`, {
next: { tags: ["inventoryLotLines"] },
});
});

+ 36
- 1
src/app/api/inventory/index.ts Ver arquivo

@@ -1,3 +1,4 @@
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
@@ -5,20 +6,54 @@ import "server-only";

export interface InventoryResult {
id: number;
itemId: number;
itemCode: string;
itemName: string;
itemType: string;
onHandQty: number;
onHoldQty: number;
unavailableQty: number;
availableQty: number;
uomCode: string;
uomUdfudesc: string;
// germPerSmallestUnit: number;
// qtyPerSmallestUnit: number;
qtyPerSmallestUnit: number;
baseUom: string;
// smallestUnit: string;
price: number;
currencyName: string;
status: string;
}

export interface InventoryLotLineResult {
id: number;
lotNo: string;
item: InventoryLotLineItem;
warehouse: InventoryLotLineWarehouse;
inQty: number;
outQty: number;
holdQty: number;
expiryDate: number[];
status: string;
availableQty: number;
uom: string;
qtyPerSmallestUnit: number;
baseUom: string;
}

export interface InventoryLotLineItem {
id: number;
code: string;
name: string;
type: string;
}

export interface InventoryLotLineWarehouse {
id: number;
code: string;
name: string;
}

export const preloadInventory = () => {
fetchInventories();
};


+ 4
- 0
src/app/api/po/actions.ts Ver arquivo

@@ -39,6 +39,8 @@ export interface PurchaseQcResult {
export interface StockInInput {
status: string;
productLotNo?: string;
dnNo?: string;
invoiceNo?: string;
receiptDate: string;
acceptedQty: number;
acceptedWeight?: number;
@@ -55,7 +57,9 @@ export interface PurchaseQCInput {
}
export interface EscalationInput {
status: string;
remarks?: string;
handler: string;
productLotNo: string;
acceptedQty: number; // this is the qty to be escalated
// escalationQty: number
}


+ 7
- 0
src/app/utils/commonUtil.ts Ver arquivo

@@ -26,3 +26,10 @@ export const convertObjToURLSearchParams = <T extends object>(

return params.toString();
};

export const getCustomWidth = (): number => {
const LIMIT_WIDTH = 1000
const CUSTOM_WIDTH = 1100
if (window.innerWidth < LIMIT_WIDTH) return CUSTOM_WIDTH
return window.innerWidth
}

+ 2
- 1
src/app/utils/formatUtil.ts Ver arquivo

@@ -43,7 +43,8 @@ export const arrayToDayjs = (arr: ConfigType | (number | undefined)[]) => {

if (isArray(arr) && every(arr, isValidNumber) && arr.length >= 3) {
// [year, month, day]
tempArr = take(arr, 3);
// tempArr = take(arr, 3);
tempArr = `${arr[0]?.toString().padStart(4, "0")}-${arr[1]?.toString().padStart(2, "0")}-${arr[2]?.toString().padStart(2, "0")}`;
}

return dayjs(tempArr as ConfigType);


+ 14
- 8
src/components/InputDataGrid/InputDataGrid.tsx Ver arquivo

@@ -37,7 +37,9 @@ import {
GridApiCommunity,
GridSlotsComponentsProps,
} from "@mui/x-data-grid/internals";

// T == CreatexxxInputs map of the form's fields
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc
// E == error
interface ResultWithId {
id: string | number;
}
@@ -97,7 +99,7 @@ export class ProcessRowUpdateError<T, E> extends Error {
Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
}
}
// T == CreatexxxInputs
// T == CreatexxxInputs map of the form's fields
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc
// E == error
function InputDataGrid<T, V, E>({
@@ -126,7 +128,11 @@ function InputDataGrid<T, V, E>({
const list: TableRow<V, E>[] = getValues(formKey);
return list && list.length > 0 ? list : [];
});
const originalRows = list && list.length > 0 ? list : [];
// const originalRows = list && list.length > 0 ? list : [];
const originalRows = useMemo(() => (
list && list.length > 0 ? list : []
), [list])
// const originalRowModel = originalRows.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel
const [rowSelectionModel, setRowSelectionModel] =
useState<GridRowSelectionModel>(() => {
@@ -154,7 +160,7 @@ function InputDataGrid<T, V, E>({
console.log(errors);
apiRef.current.updateRows([{ ...row, _error: errors }]);
},
[apiRef, rowModesModel],
[apiRef],
);

const processRowUpdate = useCallback(
@@ -202,7 +208,7 @@ function InputDataGrid<T, V, E>({
const reset = useCallback(() => {
setRowModesModel({});
setRows(originalRows);
}, []);
}, [originalRows]);

const handleCancel = useCallback(
(id: GridRowId) => () => {
@@ -219,14 +225,14 @@ function InputDataGrid<T, V, E>({
);
}
},
[setRowModesModel, rows],
[rows, getRowId],
);

const handleDelete = useCallback(
(id: GridRowId) => () => {
setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id));
},
[],
[getRowId],
);

const _columns = useMemo<GridColDef[]>(
@@ -281,7 +287,7 @@ function InputDataGrid<T, V, E>({
// console.log(formKey)
// console.log(rows)
setValue(formKey, rows);
}, [formKey, rows]);
}, [formKey, rows, setValue]);

const footer = (
<Box display="flex" gap={2} alignItems="center">


+ 110
- 0
src/components/InventorySearch/InventoryLotLineTable.tsx Ver arquivo

@@ -0,0 +1,110 @@
import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory";
import { Dispatch, SetStateAction, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Column } from "../SearchResults";
import SearchResults, { defaultPagingController, defaultSetPagingController } from "../SearchResults/SearchResults";
import { CheckCircleOutline, DoDisturb } from "@mui/icons-material";
import { arrayToDateString } from "@/app/utils/formatUtil";
import { Typography } from "@mui/material";
import { isFinite } from "lodash";

interface Props {
inventoryLotLines: InventoryLotLineResult[] | null;
setPagingController: defaultSetPagingController;
pagingController: typeof defaultPagingController;
totalCount: number;
item: InventoryResult | null;
}

const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingController, setPagingController, totalCount, item }) => {
const { t } = useTranslation(["inventory"]);

const columns = useMemo<Column<InventoryLotLineResult>[]>(
() => [
// {
// name: "item",
// label: t("Code"),
// renderCell: (params) => {
// return params.item.code;
// },
// },
// {
// name: "item",
// label: t("Name"),
// renderCell: (params) => {
// return params.item.name;
// },
// },
{
name: "lotNo",
label: t("Lot No"),
},
// {
// name: "item",
// label: t("Type"),
// renderCell: (params) => {
// return t(params.item.type);
// },
// },
{
name: "availableQty",
label: t("Available Qty"),
align: "right",
headerAlign: "right",
type: "integer",
},
{
name: "uom",
label: t("Sales UoM"),
align: "left",
headerAlign: "left",
},
{
name: "qtyPerSmallestUnit",
label: t("Available Qty Per Smallest Unit"),
align: "right",
headerAlign: "right",
type: "integer",
},
{
name: "baseUom",
label: t("Base UoM"),
align: "left",
headerAlign: "left",
},
{
name: "expiryDate",
label: t("Expiry Date"),
renderCell: (params) => {
return arrayToDateString(params.expiryDate)
},
},
// {
// name: "status",
// label: t("Status"),
// type: "icon",
// icons: {
// available: <CheckCircleOutline fontSize="small"/>,
// unavailable: <DoDisturb fontSize="small"/>,
// },
// colors: {
// available: "success",
// unavailable: "error",
// }
// },
],
[t],
);
return <>
<Typography variant="h6">{item ? `${t("Item selected")}: ${item.itemCode} | ${item.itemName} (${t(item.itemType)})` : t("No items are selected yet.")}</Typography>
<SearchResults<InventoryLotLineResult>
items={inventoryLotLines ?? []}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
totalCount={totalCount}
/>
</>
}

export default InventoryLotLineTable;

+ 147
- 99
src/components/InventorySearch/InventorySearch.tsx Ver arquivo

@@ -1,11 +1,15 @@
"use client";
import { InventoryResult } from "@/app/api/inventory";
import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory";
import { useTranslation } from "react-i18next";
import SearchBox, { Criterion } from "../SearchBox";
import { useCallback, useMemo, useState } from "react";
import { uniq } from "lodash";
import { useCallback, useEffect, useMemo, useState } from "react";
import { orderBy, uniq, uniqBy } from "lodash";
import SearchResults, { Column } from "../SearchResults";
import { CheckCircleOutline, DoDisturb } from "@mui/icons-material";
import InventoryTable from "./InventoryTable";
import { defaultPagingController } from "../SearchResults/SearchResults";
import InventoryLotLineTable from "./InventoryLotLineTable";
import { SearchInventory, SearchInventoryLotLine, fetchInventories, fetchInventoryLotLines } from "@/app/api/inventory/actions";

interface Props {
inventories: InventoryResult[];
@@ -31,7 +35,30 @@ type SearchParamNames = keyof SearchQuery;
const InventorySearch: React.FC<Props> = ({ inventories }) => {
const { t } = useTranslation(["inventory", "common"]);

const [filteredInventories, setFilteredInventories] = useState(inventories);
// Inventory
const [filteredInventories, setFilteredInventories] = useState<InventoryResult[]>([]);
const [inventoriesPagingController, setInventoriesPagingController] = useState(defaultPagingController)
const [inventoriesTotalCount, setInventoriesTotalCount] = useState(0)
const [item, setItem] = useState<InventoryResult | null>(null)

// Inventory Lot Line
const [filteredInventoryLotLines, setFilteredInventoryLotLines] = useState<InventoryLotLineResult[]>([]);
const [inventoryLotLinesPagingController, setInventoryLotLinesPagingController] = useState(defaultPagingController)
const [inventoryLotLinesTotalCount, setInventoryLotLinesTotalCount] = useState(0)

const [inputs, setInputs] = useState<Record<SearchParamNames, string>>({
itemId: "",
itemCode: "",
itemName: "",
itemType: "",
onHandQty: "",
onHoldQty: "",
unavailableQty: "",
availableQty: "",
currencyName: "",
status: "",
baseUom: "",
});

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
@@ -43,88 +70,102 @@ const InventorySearch: React.FC<Props> = ({ inventories }) => {
type: "select",
options: uniq(inventories.map((i) => i.itemType)),
},
{
label: t("Status"),
paramName: "status",
type: "select",
options: uniq(inventories.map((i) => i.status)),
},
// {
// label: t("Status"),
// paramName: "status",
// type: "select",
// options: uniq(inventories.map((i) => i.status)),
// },
],
[t],
);

const refetchInventoryData = useCallback(async (
query: Record<SearchParamNames, string>,
actionType: "reset" | "search" | "paging"
) => {
const params: SearchInventory = {
code: query?.itemCode ?? '',
name: query?.itemName ?? '',
type: query?.itemType.toLowerCase() === "all" ? '' : query?.itemType ?? '',
pageNum: inventoriesPagingController.pageNum - 1,
pageSize: inventoriesPagingController.pageSize
}

const response = await fetchInventories(params)

if (response) {
console.log(response)
setInventoriesTotalCount(response.total);
switch (actionType) {
case "reset":
case "search":
setFilteredInventories(() => response.records);
break;
case "paging":
setFilteredInventories((fi) =>
orderBy(
uniqBy([...fi, ...response.records], "id"),
["id"], ["desc"]
));
}
}
}, [inventoriesPagingController, setInventoriesPagingController])

useEffect(() => {
refetchInventoryData(inputs, "paging")
}, [inventoriesPagingController])

const refetchInventoryLotLineData = useCallback(async (
itemId: number | null,
actionType: "reset" | "search" | "paging"
) => {
if (!itemId) {
setItem(null)
setInventoryLotLinesTotalCount(0);
setFilteredInventoryLotLines([])
return
}

const params: SearchInventoryLotLine = {
itemId: itemId,
pageNum: inventoriesPagingController.pageNum - 1,
pageSize: inventoriesPagingController.pageSize
}

const response = await fetchInventoryLotLines(params)

if (response) {
setInventoryLotLinesTotalCount(response.total);
switch (actionType) {
case "reset":
case "search":
setFilteredInventoryLotLines(() => response.records);
break;
case "paging":
setFilteredInventoryLotLines((fi) =>
orderBy(
uniqBy([...fi, ...response.records], "id"),
["id"], ["desc"]
));
}
}
}, [inventoriesPagingController, setInventoriesPagingController])

useEffect(() => {
refetchInventoryData(inputs, "paging")
}, [inventoriesPagingController])

const onReset = useCallback(() => {
setFilteredInventories(inventories);
}, [inventories]);
refetchInventoryData(inputs, "reset");
refetchInventoryLotLineData(null, "reset");
// setFilteredInventories(inventories);
}, []);

const columns = useMemo<Column<InventoryResult>[]>(
() => [
{
name: "itemCode",
label: t("Code"),
},
{
name: "itemName",
label: t("Name"),
},
{
name: "itemType",
label: t("Type"),
renderCell: (params) => {
return t(params.itemType);
},
},
{
name: "availableQty",
label: t("Qty"),
align: "right",
headerAlign: "right",
type: "integer",
},
{
name: "uomUdfudesc",
label: t("UoM"),
},
// {
// name: "qtyPerSmallestUnit",
// label: t("Qty Per Smallest Unit"),
// align: "right",
// headerAlign: "right",
// type: "decimal"
// },
// {
// name: "smallestUnit",
// label: t("Smallest Unit"),
// },
// {
// name: "price",
// label: t("Price"),
// align: "right",
// sx: {
// alignItems: "right",
// justifyContent: "end",
// }
// },
// {
// name: "currencyName",
// label: t("Currency"),
// },
// {
// name: "status",
// label: t("Status"),
// type: "icon",
// icons: {
// available: <CheckCircleOutline fontSize="small"/>,
// unavailable: <DoDisturb fontSize="small"/>,
// },
// colors: {
// available: "success",
// unavailable: "error",
// }
// },
],
[t],
);
const onInventoryRowClick = useCallback((item: InventoryResult) => {
setItem(item)
refetchInventoryLotLineData(item.itemId, "search")
}, [])

return (
<>
@@ -133,28 +174,35 @@ const InventorySearch: React.FC<Props> = ({ inventories }) => {
onSearch={(query) => {
// console.log(query)
// console.log(inventories)
setFilteredInventories(
inventories.filter(
(i) =>
i.itemCode.toLowerCase().includes(query.itemCode.toLowerCase()) &&
i.itemName.toLowerCase().includes(query.itemName.toLowerCase()) &&
(query.itemType == "All" ||
i.itemType.toLowerCase().includes(query.itemType.toLowerCase())) &&
(query.status == "All" ||
i.status.toLowerCase().includes(query.status.toLowerCase())),
),
);
setInputs(() => query)
refetchInventoryData(query, "search")
// setFilteredInventories(
// inventories.filter(
// (i) =>
// i.itemCode.toLowerCase().includes(query.itemCode.toLowerCase()) &&
// i.itemName.toLowerCase().includes(query.itemName.toLowerCase()) &&
// (query.itemType == "All" ||
// i.itemType.toLowerCase().includes(query.itemType.toLowerCase())) &&
// (query.status == "All" ||
// i.status.toLowerCase().includes(query.status.toLowerCase())),
// ),
// );
}}
onReset={onReset}
/>
<SearchResults<InventoryResult>
items={filteredInventories}
columns={columns}
pagingController={{
pageNum: 0,
pageSize: 0,
// totalCount: 0,
}}
<InventoryTable
inventories={filteredInventories}
pagingController={inventoriesPagingController}
setPagingController={setInventoriesPagingController}
totalCount={inventoriesTotalCount}
onRowClick={onInventoryRowClick}
/>
<InventoryLotLineTable
inventoryLotLines={filteredInventoryLotLines}
pagingController={inventoryLotLinesPagingController}
setPagingController={setInventoryLotLinesPagingController}
totalCount={inventoryLotLinesTotalCount}
item={item}
/>
</>
);


+ 111
- 0
src/components/InventorySearch/InventoryTable.tsx Ver arquivo

@@ -0,0 +1,111 @@
import { InventoryResult } from "@/app/api/inventory";
import { Dispatch, SetStateAction, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Column } from "../SearchResults";
import SearchResults, { defaultPagingController, defaultSetPagingController } from "../SearchResults/SearchResults";

interface Props {
inventories: InventoryResult[];
setPagingController: defaultSetPagingController;
pagingController: typeof defaultPagingController;
totalCount: number;
onRowClick: (item: InventoryResult) => void;
}

const InventoryTable: React.FC<Props> = ({ inventories, pagingController, setPagingController, totalCount, onRowClick }) => {
const { t } = useTranslation(["inventory"]);

const columns = useMemo<Column<InventoryResult>[]>(
() => [
{
name: "itemCode",
label: t("Code"),
},
{
name: "itemName",
label: t("Name"),
},
{
name: "itemType",
label: t("Type"),
renderCell: (params) => {
return t(params.itemType);
},
},
{
name: "availableQty",
label: t("Available Qty"),
align: "right",
headerAlign: "right",
type: "integer",
},
{
name: "uomUdfudesc",
label: t("Sales UoM"),
align: "left",
headerAlign: "left",
},
{
name: "qtyPerSmallestUnit",
label: t("Available Qty Per Smallest Unit"),
align: "right",
headerAlign: "right",
type: "integer",
},
{
name: "baseUom",
label: t("Base UoM"),
align: "left",
headerAlign: "left",
},
// {
// name: "qtyPerSmallestUnit",
// label: t("Qty Per Smallest Unit"),
// align: "right",
// headerAlign: "right",
// type: "decimal"
// },
// {
// name: "smallestUnit",
// label: t("Smallest Unit"),
// },
// {
// name: "price",
// label: t("Price"),
// align: "right",
// sx: {
// alignItems: "right",
// justifyContent: "end",
// }
// },
// {
// name: "currencyName",
// label: t("Currency"),
// },
// {
// name: "status",
// label: t("Status"),
// type: "icon",
// icons: {
// available: <CheckCircleOutline fontSize="small"/>,
// unavailable: <DoDisturb fontSize="small"/>,
// },
// colors: {
// available: "success",
// unavailable: "error",
// }
// },
],
[t],
);
return <SearchResults<InventoryResult>
items={inventories}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
totalCount={totalCount}
onRowClick={onRowClick}
/>
}

export default InventoryTable;

+ 13
- 12
src/components/PickOrderDetail/PickOrderDetail.tsx Ver arquivo

@@ -137,11 +137,11 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode, qc }) => {

const pickOrderLineColumns = useMemo<GridColDef[]>(
() => [
{
field: "id",
headerName: "pickOrderLineId",
flex: 1,
},
// {
// field: "id",
// headerName: "pickOrderLineId",
// flex: 1,
// },
{
field: "itemName",
headerName: t("item"),
@@ -643,7 +643,8 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode, qc }) => {
const homemade_Qrcode = {
// stockInLineId: 156, // eggs
// stockInLineId: 162, // chicken wings
stockInLineId: 168, // sesame
// stockInLineId: 168, // sesame
warehouseId: 2
};

useEffect(() => {
@@ -677,12 +678,12 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode, qc }) => {
setIsUploading,
]);

const mannuallyAddRow = useCallback(() => {
getLotDetail(homemade_Qrcode.stockInLineId).then((qrcode) => {
addRow(qrcode);
// scanner.resetScan();
});
}, [addRow, getLotDetail, homemade_Qrcode.stockInLineId]);
// const mannuallyAddRow = useCallback(() => {
// getLotDetail(homemade_Qrcode.stockInLineId).then((qrcode) => {
// addRow(qrcode);
// // scanner.resetScan();
// });
// }, [addRow, getLotDetail, homemade_Qrcode.stockInLineId]);

const validation = useCallback(
(


+ 2
- 1
src/components/PickOrderSearch/ConsolidatedPickOrders.tsx Ver arquivo

@@ -64,7 +64,8 @@ const style = {
pt: 5,
px: 5,
pb: 10,
width: 1500,
// width: 1500,
width: { xs: "100%", sm: "100%", md: "100%" },
};
interface DisableButton {
releaseBtn: boolean;


+ 39
- 36
src/components/PickOrderSearch/CreateForm.tsx Ver arquivo

@@ -107,6 +107,7 @@ const CreateForm: React.FC<Props> = ({ items }) => {
{
field: "itemId",
headerName: t("Item"),
// width: 100,
flex: 1,
editable: true,
valueFormatter(params) {
@@ -162,6 +163,7 @@ const CreateForm: React.FC<Props> = ({ items }) => {
{
field: "qty",
headerName: t("qty"),
// width: 100,
flex: 1,
type: "number",
editable: true,
@@ -181,6 +183,7 @@ const CreateForm: React.FC<Props> = ({ items }) => {
{
field: "uom",
headerName: t("uom"),
// width: 100,
flex: 1,
editable: true,
// renderEditCell(params: GridRenderEditCellParams<PolRow>) {
@@ -257,42 +260,42 @@ const CreateForm: React.FC<Props> = ({ items }) => {
</FormControl>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="targetDate"
// rules={{ required: !Boolean(productionDate) }}
render={({ field }) => {
return (
<LocalizationProvider
dateAdapter={AdapterDayjs}
adapterLocale={`${language}-hk`}
>
<DatePicker
{...field}
sx={{ width: "100%" }}
label={t("targetDate")}
value={targetDate ? dayjs(targetDate) : undefined}
onChange={(date) => {
console.log(date);
if (!date) return;
console.log(date.format(INPUT_DATE_FORMAT));
setValue("targetDate", date.format(INPUT_DATE_FORMAT));
// field.onChange(date);
}}
inputRef={field.ref}
slotProps={{
textField: {
// required: true,
error: Boolean(errors.targetDate?.message),
helperText: errors.targetDate?.message,
},
}}
/>
</LocalizationProvider>
);
}}
/>
</Grid>
<Controller
control={control}
name="targetDate"
// rules={{ required: !Boolean(productionDate) }}
render={({ field }) => {
return (
<LocalizationProvider
dateAdapter={AdapterDayjs}
adapterLocale={`${language}-hk`}
>
<DatePicker
{...field}
sx={{ width: "100%" }}
label={t("targetDate")}
value={targetDate ? dayjs(targetDate) : undefined}
onChange={(date) => {
console.log(date);
if (!date) return;
console.log(date.format(INPUT_DATE_FORMAT));
setValue("targetDate", date.format(INPUT_DATE_FORMAT));
// field.onChange(date);
}}
inputRef={field.ref}
slotProps={{
textField: {
// required: true,
error: Boolean(errors.targetDate?.message),
helperText: errors.targetDate?.message,
},
}}
/>
</LocalizationProvider>
);
}}
/>
</Grid>
</Grid>
<Grid
container


+ 2
- 2
src/components/PickOrderSearch/CreatePickOrderModal.tsx Ver arquivo

@@ -21,7 +21,7 @@ const style = {
px: 5,
pb: 10,
display: "block",
width: { xs: "60%", sm: "60%", md: "60%" },
width: { xs: "100%", sm: "100%", md: "100%" },
};

interface Props extends Omit<ModalProps, "children"> {
@@ -62,7 +62,7 @@ const CreatePickOrderModal: React.FC<Props> = ({
return (
<>
<FormProvider {...formProps}>
<Modal open={open} onClose={closeHandler} sx={{ overflowY: "scroll" }}>
<Modal open={open} onClose={closeHandler}>
<Box
sx={style}
component="form"


+ 31
- 1
src/components/PoDetail/EscalationForm.tsx Ver arquivo

@@ -87,7 +87,7 @@ const EscalationForm: React.FC<Props> = ({
useEffect(() => {
console.log("triggered");
setValue("status", status);
}, []);
}, [setValue, status]);

return (
<Grid container justifyContent="flex-start" alignItems="flex-start">
@@ -108,6 +108,18 @@ const EscalationForm: React.FC<Props> = ({
spacing={2}
sx={{ mt: 0.5 }}
>
<Grid item xs={12}>
<TextField
label={t("productLotNo")}
fullWidth
{...register("productLotNo", {
required: "productLotNo required!",
})}
disabled={disabled}
error={Boolean(errors.productLotNo)}
helperText={errors.productLotNo?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("reportQty")}
@@ -124,6 +136,24 @@ const EscalationForm: React.FC<Props> = ({
helperText={errors.acceptedQty?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("uom")}
fullWidth
disabled={true}
defaultValue={itemDetail.uom.code}
/>
</Grid>
<Grid item xs={12}>
<TextField
label={t("remarks")}
fullWidth
{...register("remarks", {

})}
disabled={disabled}
/>
</Grid>
</Grid>
<Grid
container


+ 10
- 35
src/components/PoDetail/PoDetail.tsx Ver arquivo

@@ -41,41 +41,27 @@ import {
fetchStockInLineInfo,
PurchaseQcResult,
startPo,
testFetch,
} from "@/app/api/po/actions";
import {
use,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { FormProvider, useForm } from "react-hook-form";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import InputDataGrid, {
TableRow as InputTableRow,
} from "../InputDataGrid/InputDataGrid";
import PoInputGrid from "./PoInputGrid";
import { QcItemWithChecks } from "@/app/api/qc";
import { useSearchParams } from "next/navigation";
import { WarehouseResult } from "@/app/api/warehouse";
import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil";
import QrCodeScanner from "../QrCodeScanner";
import { CameraDevice, Html5Qrcode } from "html5-qrcode";
import { CameraContext } from "../Cameras/CameraProvider";
import StyledDataGrid from "../StyledDataGrid";
import { QrCodeInfo } from "@/app/api/qrcode";
import { fetchQcResult } from "@/app/api/qc/actions";
import PoQcStockInModal from "./PoQcStockInModal";
import ReactQrCodeScannerModal, {
ScannerConfig,
} from "../ReactQrCodeScanner/ReactQrCodeScanner";
import QrModal from "./QrModal";
import { PlayArrow } from "@mui/icons-material";
import DoneIcon from "@mui/icons-material/Done";
import { QrCode } from "../QrCode";
import { getCustomWidth } from "@/app/utils/commonUtil";

type Props = {
po: PoResult;
@@ -106,14 +92,14 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
console.log(checkRes);
const newPo = await fetchPoInClient(purchaseOrder.id);
setPurchaseOrder(newPo);
}, [checkPolAndCompletePo, fetchPoInClient]);
}, [purchaseOrder.id]);

const handleStartPo = useCallback(async () => {
const startRes = await startPo(purchaseOrder.id);
console.log(startRes);
const newPo = await fetchPoInClient(purchaseOrder.id);
setPurchaseOrder(newPo);
}, [startPo, fetchPoInClient]);
}, [purchaseOrder.id]);

useEffect(() => {
setRows(purchaseOrder.pol || []);
@@ -127,11 +113,11 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const [stockInLine, setStockInLine] = useState(row.stockInLine);
const totalWeight = useMemo(
() => calculateWeight(row.qty, row.uom),
[calculateWeight],
[row.qty, row.uom],
);
const weightUnit = useMemo(
() => returnWeightUnit(row.uom),
[returnWeightUnit],
[row.uom],
);

useEffect(() => {
@@ -142,7 +128,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
} else {
setCurrStatus("pending".toUpperCase());
}
}, [processedQty]);
}, [processedQty, row.qty]);

return (
<>
@@ -272,8 +258,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
};
// break;
}
}, [purchaseOrder, handleStartPo, handleCompletePo]);
}, [purchaseOrder.status, t, handleStartPo, handleCompletePo]);

console.log(window.innerWidth)
return (
<>
<Stack
@@ -301,20 +288,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
{buttonData.buttonText}
</Button>
</Grid>
{/* {purchaseOrder.status.toLowerCase() === "pending" && (
<Grid item>
<Button onClick={handleStartPo}>Start</Button>
</Grid>
)}
{purchaseOrder.status.toLowerCase() === "receiving" && (
<Grid item>
<Button onClick={handleCompletePo}>Complete</Button>
</Grid>
)} */}
</Grid>
{/* <Grid container xs={12} justifyContent="space-between">
<Button onClick={handleCompletePo}>Complete</Button>
</Grid> */}
<Grid container xs={12} justifyContent="space-between">
<Grid item xs={8}>
<Tabs
@@ -346,7 +320,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
</Grid>
{/* tab 1 */}
<Grid sx={{ display: tabIndex === 0 ? "block" : "none" }}>
<TableContainer component={Paper}>
<TableContainer component={Paper} sx={{ width: 'fit-content', overflow: 'auto' }}>
{/* <TableContainer component={Paper} sx={{width: getCustomWidth(), overflow: 'auto' }}> */}
<Table aria-label="collapsible table" stickyHeader>
<TableHead>
<TableRow>


+ 80
- 72
src/components/PoDetail/PoInputGrid.tsx Ver arquivo

@@ -147,7 +147,7 @@ function PoInputGrid({
0,
);
setProcessedQty(processedQty);
}, [entries]);
}, [entries, setProcessedQty]);

const handleDelete = useCallback(
(id: GridRowId) => () => {
@@ -155,6 +155,42 @@ function PoInputGrid({
},
[getRowId],
);
const closeQcModal = useCallback(() => {
setQcOpen(false);
}, []);
const openQcModal = useCallback(() => {
setQcOpen(true);
}, []);

const closeStockInModal = useCallback(() => {
setStockInOpen(false);
}, []);
const openStockInModal = useCallback(() => {
setStockInOpen(true);
}, []);

const closePutAwayModal = useCallback(() => {
setPutAwayOpen(false);
}, []);
const openPutAwayModal = useCallback(() => {
setPutAwayOpen(true);
}, []);

const closeEscalationModal = useCallback(() => {
setEscalOpen(false);
}, []);
const openEscalationModal = useCallback(() => {
setEscalOpen(true);
}, []);

const closeRejectModal = useCallback(() => {
setRejectOpen(false);
}, []);
const openRejectModal = useCallback(() => {
setRejectOpen(true);
}, []);

const handleStart = useCallback(
(id: GridRowId, params: any) => () => {
setBtnIsLoading(true);
@@ -189,7 +225,7 @@ function PoInputGrid({
// openStartModal();
}, 200);
},
[createStockInLine],
[setStockInLine],
);
const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => {
return await fetchQcResult(stockInLineId as number);
@@ -217,7 +253,7 @@ function PoInputGrid({
setBtnIsLoading(false);
}, 200);
},
[fetchQcDefaultValue],
[fetchQcDefaultValue, openQcModal],
);
const handleEscalation = useCallback(
(id: GridRowId, params: any) => () => {
@@ -234,7 +270,7 @@ function PoInputGrid({
// setBtnIsLoading(false);
}, 200);
},
[],
[openEscalationModal],
);

const handleReject = useCallback(
@@ -254,7 +290,7 @@ function PoInputGrid({
// printQrcode(params.row);
}, 200);
},
[],
[openRejectModal],
);

const handleStockIn = useCallback(
@@ -274,7 +310,7 @@ function PoInputGrid({
// setBtnIsLoading(false);
}, 200);
},
[],
[openStockInModal],
);

const handlePutAway = useCallback(
@@ -294,7 +330,7 @@ function PoInputGrid({
// setBtnIsLoading(false);
}, 200);
},
[],
[openPutAwayModal],
);

const printQrcode = useCallback(
@@ -310,79 +346,47 @@ function PoInputGrid({
}
setBtnIsLoading(false);
},
[fetchPoQrcode, downloadFile],
);

const handleQrCode = useCallback(
(id: GridRowId, params: any) => () => {
setRowModesModel((prev) => ({
...prev,
[id]: { mode: GridRowModes.View },
}));
setModalInfo(params.row);
setTimeout(() => {
// open stock in modal
// openPutAwayModal();
// return the record with its status as pending
// update layout
console.log("delayed");
printQrcode(params.row);
}, 200);
},
[],
);

const closeQcModal = useCallback(() => {
setQcOpen(false);
}, []);
const openQcModal = useCallback(() => {
setQcOpen(true);
}, []);

const closeStockInModal = useCallback(() => {
setStockInOpen(false);
}, []);
const openStockInModal = useCallback(() => {
setStockInOpen(true);
}, []);

const closePutAwayModal = useCallback(() => {
setPutAwayOpen(false);
}, []);
const openPutAwayModal = useCallback(() => {
setPutAwayOpen(true);
}, []);

const closeEscalationModal = useCallback(() => {
setEscalOpen(false);
}, []);
const openEscalationModal = useCallback(() => {
setEscalOpen(true);
}, []);

const closeRejectModal = useCallback(() => {
setRejectOpen(false);
}, []);
const openRejectModal = useCallback(() => {
setRejectOpen(true);
}, []);
// const handleQrCode = useCallback(
// (id: GridRowId, params: any) => () => {
// setRowModesModel((prev) => ({
// ...prev,
// [id]: { mode: GridRowModes.View },
// }));
// setModalInfo(params.row);
// setTimeout(() => {
// // open stock in modal
// // openPutAwayModal();
// // return the record with its status as pending
// // update layout
// console.log("delayed");
// printQrcode(params.row);
// }, 200);
// },
// [printQrcode],
// );

const columns = useMemo<GridColDef[]>(
() => [
{
field: "itemNo",
headerName: t("itemNo"),
flex: 0.4,
width: 120,
// flex: 0.4,
},
{
field: "itemName",
headerName: t("itemName"),
flex: 0.6,
width: 120,
// flex: 0.6,
},
{
field: "acceptedQty",
headerName: t("acceptedQty"),
flex: 0.5,
// flex: 0.5,
width: 120,
type: "number",
// editable: true,
// replace with tooltip + content
@@ -390,7 +394,8 @@ function PoInputGrid({
{
field: "uom",
headerName: t("uom"),
flex: 0.5,
width: 120,
// flex: 0.5,
renderCell: (params) => {
return params.row.uom.code;
},
@@ -398,7 +403,8 @@ function PoInputGrid({
{
field: "weight",
headerName: t("weight"),
flex: 0.5,
width: 120,
// flex: 0.5,
renderCell: (params) => {
const weight = calculateWeight(
params.row.acceptedQty,
@@ -411,7 +417,8 @@ function PoInputGrid({
{
field: "status",
headerName: t("status"),
flex: 0.5,
width: 120,
// flex: 0.5,
renderCell: (params) => {
return t(`${params.row.status}`);
},
@@ -423,7 +430,8 @@ function PoInputGrid({
"stock in",
)} | ${t("putaway")} | ${t("delete")}`,
// headerName: "start | qc | escalation | stock in | putaway | delete",
flex: 1.5,
width: 300,
// flex: 1.5,
cellClassName: "actions",
getActions: (params) => {
// console.log(params.row.status);
@@ -494,7 +502,7 @@ function PoInputGrid({
(stockInLineStatusMap[status] >= 3 &&
stockInLineStatusMap[status] <= 5 &&
!session?.user?.abilities?.includes("APPROVAL"))
}
}
// set _isNew to false after posting
// or check status
onClick={handleStockIn(params.row.id, params)}
@@ -560,7 +568,7 @@ function PoInputGrid({
},
},
],
[stockInLineStatusMap, btnIsLoading, handleQrCode, handleReject],
[t, handleStart, handleQC, handleEscalation, session?.user?.abilities, handleStockIn, handlePutAway, handleDelete, handleReject],
);

const addRow = useCallback(() => {
@@ -585,7 +593,7 @@ function PoInputGrid({
// fieldToFocus: "projectId",
},
}));
}, [currQty, getRowId]);
}, [currQty, getRowId, itemDetail]);
const validation = useCallback(
(
newRow: GridRowModel<StockInLineRow>,
@@ -599,7 +607,7 @@ function PoInputGrid({
}
return Object.keys(error).length > 0 ? error : undefined;
},
[currQty],
[currQty, itemDetail.qty, t],
);
const processRowUpdate = useCallback(
(
@@ -632,7 +640,7 @@ function PoInputGrid({
setCurrQty(total);
return rowToSave;
},
[getRowId, entries],
[validation, entries, setStockInLine, getRowId],
);

const onProcessRowUpdateError = useCallback(


+ 31
- 1
src/components/PoDetail/PoQcStockInModal.tsx Ver arquivo

@@ -40,6 +40,8 @@ import { fetchPoQrcode } from "@/app/api/pdf/actions";
import UploadContext from "../UploadProvider/UploadProvider";
import useUploadContext from "../UploadProvider/useUploadContext";
import RejectForm from "./RejectForm";
import { isNullOrUndefined } from "html5-qrcode/esm/core";
import { isEmpty, isFinite } from "lodash";

dayjs.extend(arraySupport);
interface CommonProps extends Omit<ModalProps, "children"> {
@@ -153,9 +155,37 @@ const PoQcStockInModal: React.FC<Props> = ({
// }
// return date;
// }, []);
const accQty = formProps.watch("acceptedQty");
const productLotNo = formProps.watch("productLotNo");
const checkStockIn = useCallback(
(data: ModalFormInput): boolean => {
let hasErrors = false;
if (!isFinite(accQty) || accQty! <= 0 ) {
formProps.setError("acceptedQty", {
message: `${t("Accepted qty must greater than")} ${
0
}`,
type: "required",
});
hasErrors = true;
} else if (accQty! > itemDetail.acceptedQty) {
formProps.setError("acceptedQty", {
message: `${t("Accepted qty must not greater than")} ${
itemDetail.acceptedQty
}`,
type: "required",
});
hasErrors = true;
}

if (isEmpty(productLotNo)) {
formProps.setError("productLotNo", {
message: `${t("Product Lot No must not be empty")}`,
type: "required",
});
hasErrors = true;
}

if (itemDetail.shelfLife && !data.productionDate && !data.expiryDate) {
formProps.setError("productionDate", {
message: "Please provide at least one",
@@ -184,7 +214,7 @@ const PoQcStockInModal: React.FC<Props> = ({
}
return hasErrors;
},
[itemDetail, formProps],
[accQty, itemDetail.acceptedQty, itemDetail.shelfLife, productLotNo, formProps, t],
);

const checkPutaway = useCallback(


+ 1
- 1
src/components/PoDetail/QcForm.tsx Ver arquivo

@@ -97,7 +97,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
useEffect(() => {
clearErrors();
validateForm();
}, [validateForm]);
}, [clearErrors, validateForm]);

const columns = useMemo<GridColDef[]>(
() => [


+ 2
- 2
src/components/PoDetail/QcSelect.tsx Ver arquivo

@@ -30,7 +30,7 @@ const QcSelect: React.FC<Props> = ({ allQcs, value, error, onQcSelect }) => {
const filteredQc = useMemo(() => {
// do filtering here if any
return allQcs;
}, []);
}, [allQcs]);
const options = useMemo(() => {
return [
{
@@ -44,7 +44,7 @@ const QcSelect: React.FC<Props> = ({ allQcs, value, error, onQcSelect }) => {
group: "existing",
})),
];
}, [filteredQc]);
}, [t, filteredQc]);

const currentValue = options.find((o) => o.value === value) || options[0];



+ 30
- 5
src/components/PoDetail/StockInForm.tsx Ver arquivo

@@ -96,7 +96,8 @@ const StockInForm: React.FC<Props> = ({
console.log(expiryDate);
if (expiryDate) clearErrors();
if (productionDate) clearErrors();
}, [productionDate, expiryDate]);
}, [productionDate, expiryDate, clearErrors]);
return (
<Grid container justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12}>
@@ -111,6 +112,30 @@ const StockInForm: React.FC<Props> = ({
spacing={2}
sx={{ mt: 0.5 }}
>
<Grid item xs={6}>
<TextField
label={t("dnNo")}
fullWidth
{...register("dnNo", {
// required: "productLotNo required!",
})}
disabled={disabled}
// error={Boolean(errors.productLotNo)}
// helperText={errors.productLotNo?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("invoiceNo")}
fullWidth
{...register("invoiceNo", {
// required: "productLotNo required!",
})}
disabled={disabled}
// error={Boolean(errors.productLotNo)}
// helperText={errors.productLotNo?.message}
/>
</Grid>
<Grid item xs={4}>
<TextField
label={t("productLotNo")}
@@ -119,8 +144,8 @@ const StockInForm: React.FC<Props> = ({
// required: "productLotNo required!",
})}
disabled={disabled}
// error={Boolean(errors.productLotNo)}
// helperText={errors.productLotNo?.message}
error={Boolean(errors.productLotNo)}
helperText={errors.productLotNo?.message}
/>
</Grid>
<Grid item xs={4}>
@@ -171,7 +196,7 @@ const StockInForm: React.FC<Props> = ({
helperText={errors.acceptedQty?.message}
/>
</Grid>
<Grid item xs={4}>
{/* <Grid item xs={4}>
<TextField
label={t("acceptedWeight")}
fullWidth
@@ -182,7 +207,7 @@ const StockInForm: React.FC<Props> = ({
error={Boolean(errors.acceptedWeight)}
helperText={errors.acceptedWeight?.message}
/>
</Grid>
</Grid> */}
<Grid item xs={4}>
<Controller
control={control}


+ 1
- 1
src/components/SearchBox/SearchBox.tsx Ver arquivo

@@ -248,7 +248,7 @@ function SearchBox<T extends string>({
<MenuItem value={"All"}>{t("All")}</MenuItem>
{c.options.map((option) => (
<MenuItem key={option} value={option}>
{option}
{t(option)}
</MenuItem>
))}
</Select>


+ 49
- 34
src/components/SearchResults/SearchResults.tsx Ver arquivo

@@ -100,6 +100,7 @@ interface Props<T extends ResultWithId> {
isAutoPaging?: boolean;
checkboxIds?: (string | number)[];
setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>;
onRowClick?: (item: T) => void;
}

function isActionColumn<T extends ResultWithId>(
@@ -138,8 +139,8 @@ function convertObjectKeysToLowercase<T extends object>(
): object | undefined {
return obj
? Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]),
)
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]),
)
: undefined;
}

@@ -174,6 +175,14 @@ export const defaultPagingController: { pageNum: number; pageSize: number } = {
pageNum: 1,
pageSize: 10,
};

export type defaultSetPagingController = Dispatch<
SetStateAction<{
pageNum: number;
pageSize: number;
}>
>

function SearchResults<T extends ResultWithId>({
items,
columns,
@@ -184,6 +193,7 @@ function SearchResults<T extends ResultWithId>({
totalCount,
checkboxIds = [],
setCheckboxIds = undefined,
onRowClick = undefined,
}: Props<T>) {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
@@ -279,40 +289,25 @@ function SearchResults<T extends ResultWithId>({
<TableBody>
{isAutoPaging
? items
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((item) => {
return (
<TableRow
hover
tabIndex={-1}
key={item.id}
onClick={
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((item) => {
return (
<TableRow
hover
tabIndex={-1}
key={item.id}
onClick={(event) => {
setCheckboxIds
? (event) => handleRowClick(event, item, columns)
? handleRowClick(event, item, columns)
: undefined
if (onRowClick) {
onRowClick(item)
}
}
role={setCheckboxIds ? "checkbox" : undefined}
>
{columns.map((column, idx) => {
const columnName = column.name;

return (
<TabelCells
key={`${columnName.toString()}-${idx}`}
column={column}
columnName={columnName}
idx={idx}
item={item}
checkboxIds={checkboxIds}
/>
);
})}
</TableRow>
);
})
: items.map((item) => {
return (
<TableRow hover tabIndex={-1} key={item.id}>
}
role={setCheckboxIds ? "checkbox" : undefined}
>
{columns.map((column, idx) => {
const columnName = column.name;

@@ -329,7 +324,27 @@ function SearchResults<T extends ResultWithId>({
})}
</TableRow>
);
})}
})
: items.map((item) => {
return (
<TableRow hover tabIndex={-1} key={item.id}>
{columns.map((column, idx) => {
const columnName = column.name;

return (
<TabelCells
key={`${columnName.toString()}-${idx}`}
column={column}
columnName={columnName}
idx={idx}
item={item}
checkboxIds={checkboxIds}
/>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>


+ 1
- 0
src/i18n/zh/common.json Ver arquivo

@@ -35,6 +35,7 @@
"Project":"專案",
"Product":"產品",
"Material":"材料",
"mat":"原料",
"FG":"成品",
"FG & Material Demand Forecast Detail":"成品及材料需求預測詳情",
"View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌",


+ 10
- 2
src/i18n/zh/inventory.json Ver arquivo

@@ -7,5 +7,13 @@
"Qty": "數量",
"UoM": "單位",
"mat": "物料",
"fg": "成品"
}
"fg": "成品",
"Available Qty": "可用數量 (銷售單位)",
"Sales UoM": "銷售單位",
"Available Qty Per Smallest Unit": "可用數量 (基本單位)",
"Base UoM": "基本單位",
"Lot No": "批號",
"Expiry Date": "到期日",
"No items are selected yet.": "未選擇項目",
"Item selected": "已選擇項目"
}

+ 1
- 1
src/i18n/zh/purchaseOrder.json Ver arquivo

@@ -27,7 +27,7 @@
"total weight": "總重量",
"weight unit": "重量單位",
"price": "價格",
"processed": "已入倉",
"processed": "已處理",
"expiryDate": "到期日",
"acceptedQty": "接受數量",
"weight": "重量",


Carregando…
Cancelar
Salvar