@@ -5,7 +5,7 @@ | |||||
"scripts": { | "scripts": { | ||||
"dev": "next dev", | "dev": "next dev", | ||||
"build": "next build", | "build": "next build", | ||||
"start": "NODE_OPTIONS='--inspect' next start", | |||||
"start": "set NODE_OPTIONS=--inspect&& next start", | |||||
"lint": "next lint", | "lint": "next lint", | ||||
"type-check": "tsc --noEmit" | "type-check": "tsc --noEmit" | ||||
}, | }, | ||||
@@ -3,9 +3,11 @@ import { BASE_API_URL } from "@/config/api"; | |||||
// import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
import { cache } from "react"; | import { cache } from "react"; | ||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { QcItemResult } from "../settings/qcItem"; | import { QcItemResult } from "../settings/qcItem"; | ||||
import { RecordsRes } from "../utils"; | import { RecordsRes } from "../utils"; | ||||
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||||
import { InventoryLotLineResult, InventoryResult } from "."; | |||||
// import { BASE_API_URL } from "@/config/api"; | // import { BASE_API_URL } from "@/config/api"; | ||||
export interface LotLineInfo { | export interface LotLineInfo { | ||||
@@ -18,6 +20,26 @@ export interface LotLineInfo { | |||||
uom: string; | 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) => { | export const fetchLotDetail = cache(async (stockInLineId: number) => { | ||||
return serverFetchJson<LotLineInfo>( | return serverFetchJson<LotLineInfo>( | ||||
`${BASE_API_URL}/inventoryLotLine/lot-detail/${stockInLineId}`, | `${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"] }, | |||||
}); | |||||
}); |
@@ -1,3 +1,4 @@ | |||||
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { cache } from "react"; | import { cache } from "react"; | ||||
@@ -5,20 +6,54 @@ import "server-only"; | |||||
export interface InventoryResult { | export interface InventoryResult { | ||||
id: number; | id: number; | ||||
itemId: number; | |||||
itemCode: string; | itemCode: string; | ||||
itemName: string; | itemName: string; | ||||
itemType: string; | itemType: string; | ||||
onHandQty: number; | |||||
onHoldQty: number; | |||||
unavailableQty: number; | |||||
availableQty: number; | availableQty: number; | ||||
uomCode: string; | uomCode: string; | ||||
uomUdfudesc: string; | uomUdfudesc: string; | ||||
// germPerSmallestUnit: number; | // germPerSmallestUnit: number; | ||||
// qtyPerSmallestUnit: number; | |||||
qtyPerSmallestUnit: number; | |||||
baseUom: string; | |||||
// smallestUnit: string; | // smallestUnit: string; | ||||
price: number; | price: number; | ||||
currencyName: string; | currencyName: string; | ||||
status: 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 = () => { | export const preloadInventory = () => { | ||||
fetchInventories(); | fetchInventories(); | ||||
}; | }; | ||||
@@ -39,6 +39,8 @@ export interface PurchaseQcResult { | |||||
export interface StockInInput { | export interface StockInInput { | ||||
status: string; | status: string; | ||||
productLotNo?: string; | productLotNo?: string; | ||||
dnNo?: string; | |||||
invoiceNo?: string; | |||||
receiptDate: string; | receiptDate: string; | ||||
acceptedQty: number; | acceptedQty: number; | ||||
acceptedWeight?: number; | acceptedWeight?: number; | ||||
@@ -55,7 +57,9 @@ export interface PurchaseQCInput { | |||||
} | } | ||||
export interface EscalationInput { | export interface EscalationInput { | ||||
status: string; | status: string; | ||||
remarks?: string; | |||||
handler: string; | handler: string; | ||||
productLotNo: string; | |||||
acceptedQty: number; // this is the qty to be escalated | acceptedQty: number; // this is the qty to be escalated | ||||
// escalationQty: number | // escalationQty: number | ||||
} | } | ||||
@@ -26,3 +26,10 @@ export const convertObjToURLSearchParams = <T extends object>( | |||||
return params.toString(); | 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 | |||||
} |
@@ -43,7 +43,8 @@ export const arrayToDayjs = (arr: ConfigType | (number | undefined)[]) => { | |||||
if (isArray(arr) && every(arr, isValidNumber) && arr.length >= 3) { | if (isArray(arr) && every(arr, isValidNumber) && arr.length >= 3) { | ||||
// [year, month, day] | // [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); | return dayjs(tempArr as ConfigType); | ||||
@@ -37,7 +37,9 @@ import { | |||||
GridApiCommunity, | GridApiCommunity, | ||||
GridSlotsComponentsProps, | GridSlotsComponentsProps, | ||||
} from "@mui/x-data-grid/internals"; | } 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 { | interface ResultWithId { | ||||
id: string | number; | id: string | number; | ||||
} | } | ||||
@@ -97,7 +99,7 @@ export class ProcessRowUpdateError<T, E> extends Error { | |||||
Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | 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 | // V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc | ||||
// E == error | // E == error | ||||
function InputDataGrid<T, V, E>({ | function InputDataGrid<T, V, E>({ | ||||
@@ -126,7 +128,11 @@ function InputDataGrid<T, V, E>({ | |||||
const list: TableRow<V, E>[] = getValues(formKey); | const list: TableRow<V, E>[] = getValues(formKey); | ||||
return list && list.length > 0 ? list : []; | 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 originalRowModel = originalRows.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel | ||||
const [rowSelectionModel, setRowSelectionModel] = | const [rowSelectionModel, setRowSelectionModel] = | ||||
useState<GridRowSelectionModel>(() => { | useState<GridRowSelectionModel>(() => { | ||||
@@ -154,7 +160,7 @@ function InputDataGrid<T, V, E>({ | |||||
console.log(errors); | console.log(errors); | ||||
apiRef.current.updateRows([{ ...row, _error: errors }]); | apiRef.current.updateRows([{ ...row, _error: errors }]); | ||||
}, | }, | ||||
[apiRef, rowModesModel], | |||||
[apiRef], | |||||
); | ); | ||||
const processRowUpdate = useCallback( | const processRowUpdate = useCallback( | ||||
@@ -202,7 +208,7 @@ function InputDataGrid<T, V, E>({ | |||||
const reset = useCallback(() => { | const reset = useCallback(() => { | ||||
setRowModesModel({}); | setRowModesModel({}); | ||||
setRows(originalRows); | setRows(originalRows); | ||||
}, []); | |||||
}, [originalRows]); | |||||
const handleCancel = useCallback( | const handleCancel = useCallback( | ||||
(id: GridRowId) => () => { | (id: GridRowId) => () => { | ||||
@@ -219,14 +225,14 @@ function InputDataGrid<T, V, E>({ | |||||
); | ); | ||||
} | } | ||||
}, | }, | ||||
[setRowModesModel, rows], | |||||
[rows, getRowId], | |||||
); | ); | ||||
const handleDelete = useCallback( | const handleDelete = useCallback( | ||||
(id: GridRowId) => () => { | (id: GridRowId) => () => { | ||||
setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); | setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); | ||||
}, | }, | ||||
[], | |||||
[getRowId], | |||||
); | ); | ||||
const _columns = useMemo<GridColDef[]>( | const _columns = useMemo<GridColDef[]>( | ||||
@@ -281,7 +287,7 @@ function InputDataGrid<T, V, E>({ | |||||
// console.log(formKey) | // console.log(formKey) | ||||
// console.log(rows) | // console.log(rows) | ||||
setValue(formKey, rows); | setValue(formKey, rows); | ||||
}, [formKey, rows]); | |||||
}, [formKey, rows, setValue]); | |||||
const footer = ( | const footer = ( | ||||
<Box display="flex" gap={2} alignItems="center"> | <Box display="flex" gap={2} alignItems="center"> | ||||
@@ -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; |
@@ -1,11 +1,15 @@ | |||||
"use client"; | "use client"; | ||||
import { InventoryResult } from "@/app/api/inventory"; | |||||
import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import SearchBox, { Criterion } from "../SearchBox"; | 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 SearchResults, { Column } from "../SearchResults"; | ||||
import { CheckCircleOutline, DoDisturb } from "@mui/icons-material"; | 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 { | interface Props { | ||||
inventories: InventoryResult[]; | inventories: InventoryResult[]; | ||||
@@ -31,7 +35,30 @@ type SearchParamNames = keyof SearchQuery; | |||||
const InventorySearch: React.FC<Props> = ({ inventories }) => { | const InventorySearch: React.FC<Props> = ({ inventories }) => { | ||||
const { t } = useTranslation(["inventory", "common"]); | 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( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
() => [ | () => [ | ||||
@@ -43,88 +70,102 @@ const InventorySearch: React.FC<Props> = ({ inventories }) => { | |||||
type: "select", | type: "select", | ||||
options: uniq(inventories.map((i) => i.itemType)), | 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], | [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(() => { | 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 ( | return ( | ||||
<> | <> | ||||
@@ -133,28 +174,35 @@ const InventorySearch: React.FC<Props> = ({ inventories }) => { | |||||
onSearch={(query) => { | onSearch={(query) => { | ||||
// console.log(query) | // console.log(query) | ||||
// console.log(inventories) | // 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} | 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} | |||||
/> | /> | ||||
</> | </> | ||||
); | ); | ||||
@@ -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; |
@@ -137,11 +137,11 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode, qc }) => { | |||||
const pickOrderLineColumns = useMemo<GridColDef[]>( | const pickOrderLineColumns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
{ | |||||
field: "id", | |||||
headerName: "pickOrderLineId", | |||||
flex: 1, | |||||
}, | |||||
// { | |||||
// field: "id", | |||||
// headerName: "pickOrderLineId", | |||||
// flex: 1, | |||||
// }, | |||||
{ | { | ||||
field: "itemName", | field: "itemName", | ||||
headerName: t("item"), | headerName: t("item"), | ||||
@@ -643,7 +643,8 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode, qc }) => { | |||||
const homemade_Qrcode = { | const homemade_Qrcode = { | ||||
// stockInLineId: 156, // eggs | // stockInLineId: 156, // eggs | ||||
// stockInLineId: 162, // chicken wings | // stockInLineId: 162, // chicken wings | ||||
stockInLineId: 168, // sesame | |||||
// stockInLineId: 168, // sesame | |||||
warehouseId: 2 | |||||
}; | }; | ||||
useEffect(() => { | useEffect(() => { | ||||
@@ -677,12 +678,12 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode, qc }) => { | |||||
setIsUploading, | 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( | const validation = useCallback( | ||||
( | ( | ||||
@@ -64,7 +64,8 @@ const style = { | |||||
pt: 5, | pt: 5, | ||||
px: 5, | px: 5, | ||||
pb: 10, | pb: 10, | ||||
width: 1500, | |||||
// width: 1500, | |||||
width: { xs: "100%", sm: "100%", md: "100%" }, | |||||
}; | }; | ||||
interface DisableButton { | interface DisableButton { | ||||
releaseBtn: boolean; | releaseBtn: boolean; | ||||
@@ -107,6 +107,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||||
{ | { | ||||
field: "itemId", | field: "itemId", | ||||
headerName: t("Item"), | headerName: t("Item"), | ||||
// width: 100, | |||||
flex: 1, | flex: 1, | ||||
editable: true, | editable: true, | ||||
valueFormatter(params) { | valueFormatter(params) { | ||||
@@ -162,6 +163,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||||
{ | { | ||||
field: "qty", | field: "qty", | ||||
headerName: t("qty"), | headerName: t("qty"), | ||||
// width: 100, | |||||
flex: 1, | flex: 1, | ||||
type: "number", | type: "number", | ||||
editable: true, | editable: true, | ||||
@@ -181,6 +183,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||||
{ | { | ||||
field: "uom", | field: "uom", | ||||
headerName: t("uom"), | headerName: t("uom"), | ||||
// width: 100, | |||||
flex: 1, | flex: 1, | ||||
editable: true, | editable: true, | ||||
// renderEditCell(params: GridRenderEditCellParams<PolRow>) { | // renderEditCell(params: GridRenderEditCellParams<PolRow>) { | ||||
@@ -257,42 +260,42 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||||
</FormControl> | </FormControl> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <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> | ||||
<Grid | <Grid | ||||
container | container | ||||
@@ -21,7 +21,7 @@ const style = { | |||||
px: 5, | px: 5, | ||||
pb: 10, | pb: 10, | ||||
display: "block", | display: "block", | ||||
width: { xs: "60%", sm: "60%", md: "60%" }, | |||||
width: { xs: "100%", sm: "100%", md: "100%" }, | |||||
}; | }; | ||||
interface Props extends Omit<ModalProps, "children"> { | interface Props extends Omit<ModalProps, "children"> { | ||||
@@ -62,7 +62,7 @@ const CreatePickOrderModal: React.FC<Props> = ({ | |||||
return ( | return ( | ||||
<> | <> | ||||
<FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
<Modal open={open} onClose={closeHandler} sx={{ overflowY: "scroll" }}> | |||||
<Modal open={open} onClose={closeHandler}> | |||||
<Box | <Box | ||||
sx={style} | sx={style} | ||||
component="form" | component="form" | ||||
@@ -87,7 +87,7 @@ const EscalationForm: React.FC<Props> = ({ | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log("triggered"); | console.log("triggered"); | ||||
setValue("status", status); | setValue("status", status); | ||||
}, []); | |||||
}, [setValue, status]); | |||||
return ( | return ( | ||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
@@ -108,6 +108,18 @@ const EscalationForm: React.FC<Props> = ({ | |||||
spacing={2} | spacing={2} | ||||
sx={{ mt: 0.5 }} | 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}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("reportQty")} | label={t("reportQty")} | ||||
@@ -124,6 +136,24 @@ const EscalationForm: React.FC<Props> = ({ | |||||
helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
/> | /> | ||||
</Grid> | </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> | ||||
<Grid | <Grid | ||||
container | container | ||||
@@ -41,41 +41,27 @@ import { | |||||
fetchStockInLineInfo, | fetchStockInLineInfo, | ||||
PurchaseQcResult, | PurchaseQcResult, | ||||
startPo, | startPo, | ||||
testFetch, | |||||
} from "@/app/api/po/actions"; | } from "@/app/api/po/actions"; | ||||
import { | import { | ||||
use, | |||||
useCallback, | useCallback, | ||||
useContext, | useContext, | ||||
useEffect, | useEffect, | ||||
useMemo, | useMemo, | ||||
useState, | useState, | ||||
} from "react"; | } from "react"; | ||||
import { FormProvider, useForm } from "react-hook-form"; | |||||
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 InputDataGrid, { | |||||
TableRow as InputTableRow, | |||||
} from "../InputDataGrid/InputDataGrid"; | |||||
import PoInputGrid from "./PoInputGrid"; | import PoInputGrid from "./PoInputGrid"; | ||||
import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; | import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; | ||||
import QrCodeScanner from "../QrCodeScanner"; | |||||
import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | |||||
import { CameraContext } from "../Cameras/CameraProvider"; | 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 PoQcStockInModal from "./PoQcStockInModal"; | ||||
import ReactQrCodeScannerModal, { | |||||
ScannerConfig, | |||||
} from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||||
import QrModal from "./QrModal"; | import QrModal from "./QrModal"; | ||||
import { PlayArrow } from "@mui/icons-material"; | import { PlayArrow } from "@mui/icons-material"; | ||||
import DoneIcon from "@mui/icons-material/Done"; | import DoneIcon from "@mui/icons-material/Done"; | ||||
import { QrCode } from "../QrCode"; | |||||
import { getCustomWidth } from "@/app/utils/commonUtil"; | |||||
type Props = { | type Props = { | ||||
po: PoResult; | po: PoResult; | ||||
@@ -106,14 +92,14 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
console.log(checkRes); | console.log(checkRes); | ||||
const newPo = await fetchPoInClient(purchaseOrder.id); | const newPo = await fetchPoInClient(purchaseOrder.id); | ||||
setPurchaseOrder(newPo); | setPurchaseOrder(newPo); | ||||
}, [checkPolAndCompletePo, fetchPoInClient]); | |||||
}, [purchaseOrder.id]); | |||||
const handleStartPo = useCallback(async () => { | const handleStartPo = useCallback(async () => { | ||||
const startRes = await startPo(purchaseOrder.id); | const startRes = await startPo(purchaseOrder.id); | ||||
console.log(startRes); | console.log(startRes); | ||||
const newPo = await fetchPoInClient(purchaseOrder.id); | const newPo = await fetchPoInClient(purchaseOrder.id); | ||||
setPurchaseOrder(newPo); | setPurchaseOrder(newPo); | ||||
}, [startPo, fetchPoInClient]); | |||||
}, [purchaseOrder.id]); | |||||
useEffect(() => { | useEffect(() => { | ||||
setRows(purchaseOrder.pol || []); | setRows(purchaseOrder.pol || []); | ||||
@@ -127,11 +113,11 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
const [stockInLine, setStockInLine] = useState(row.stockInLine); | const [stockInLine, setStockInLine] = useState(row.stockInLine); | ||||
const totalWeight = useMemo( | const totalWeight = useMemo( | ||||
() => calculateWeight(row.qty, row.uom), | () => calculateWeight(row.qty, row.uom), | ||||
[calculateWeight], | |||||
[row.qty, row.uom], | |||||
); | ); | ||||
const weightUnit = useMemo( | const weightUnit = useMemo( | ||||
() => returnWeightUnit(row.uom), | () => returnWeightUnit(row.uom), | ||||
[returnWeightUnit], | |||||
[row.uom], | |||||
); | ); | ||||
useEffect(() => { | useEffect(() => { | ||||
@@ -142,7 +128,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
} else { | } else { | ||||
setCurrStatus("pending".toUpperCase()); | setCurrStatus("pending".toUpperCase()); | ||||
} | } | ||||
}, [processedQty]); | |||||
}, [processedQty, row.qty]); | |||||
return ( | return ( | ||||
<> | <> | ||||
@@ -272,8 +258,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
}; | }; | ||||
// break; | // break; | ||||
} | } | ||||
}, [purchaseOrder, handleStartPo, handleCompletePo]); | |||||
}, [purchaseOrder.status, t, handleStartPo, handleCompletePo]); | |||||
console.log(window.innerWidth) | |||||
return ( | return ( | ||||
<> | <> | ||||
<Stack | <Stack | ||||
@@ -301,20 +288,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
{buttonData.buttonText} | {buttonData.buttonText} | ||||
</Button> | </Button> | ||||
</Grid> | </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> | ||||
{/* <Grid container xs={12} justifyContent="space-between"> | |||||
<Button onClick={handleCompletePo}>Complete</Button> | |||||
</Grid> */} | |||||
<Grid container xs={12} justifyContent="space-between"> | <Grid container xs={12} justifyContent="space-between"> | ||||
<Grid item xs={8}> | <Grid item xs={8}> | ||||
<Tabs | <Tabs | ||||
@@ -346,7 +320,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
</Grid> | </Grid> | ||||
{/* tab 1 */} | {/* tab 1 */} | ||||
<Grid sx={{ display: tabIndex === 0 ? "block" : "none" }}> | <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> | <Table aria-label="collapsible table" stickyHeader> | ||||
<TableHead> | <TableHead> | ||||
<TableRow> | <TableRow> | ||||
@@ -147,7 +147,7 @@ function PoInputGrid({ | |||||
0, | 0, | ||||
); | ); | ||||
setProcessedQty(processedQty); | setProcessedQty(processedQty); | ||||
}, [entries]); | |||||
}, [entries, setProcessedQty]); | |||||
const handleDelete = useCallback( | const handleDelete = useCallback( | ||||
(id: GridRowId) => () => { | (id: GridRowId) => () => { | ||||
@@ -155,6 +155,42 @@ function PoInputGrid({ | |||||
}, | }, | ||||
[getRowId], | [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( | const handleStart = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
setBtnIsLoading(true); | setBtnIsLoading(true); | ||||
@@ -189,7 +225,7 @@ function PoInputGrid({ | |||||
// openStartModal(); | // openStartModal(); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[createStockInLine], | |||||
[setStockInLine], | |||||
); | ); | ||||
const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => { | const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => { | ||||
return await fetchQcResult(stockInLineId as number); | return await fetchQcResult(stockInLineId as number); | ||||
@@ -217,7 +253,7 @@ function PoInputGrid({ | |||||
setBtnIsLoading(false); | setBtnIsLoading(false); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[fetchQcDefaultValue], | |||||
[fetchQcDefaultValue, openQcModal], | |||||
); | ); | ||||
const handleEscalation = useCallback( | const handleEscalation = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
@@ -234,7 +270,7 @@ function PoInputGrid({ | |||||
// setBtnIsLoading(false); | // setBtnIsLoading(false); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[], | |||||
[openEscalationModal], | |||||
); | ); | ||||
const handleReject = useCallback( | const handleReject = useCallback( | ||||
@@ -254,7 +290,7 @@ function PoInputGrid({ | |||||
// printQrcode(params.row); | // printQrcode(params.row); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[], | |||||
[openRejectModal], | |||||
); | ); | ||||
const handleStockIn = useCallback( | const handleStockIn = useCallback( | ||||
@@ -274,7 +310,7 @@ function PoInputGrid({ | |||||
// setBtnIsLoading(false); | // setBtnIsLoading(false); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[], | |||||
[openStockInModal], | |||||
); | ); | ||||
const handlePutAway = useCallback( | const handlePutAway = useCallback( | ||||
@@ -294,7 +330,7 @@ function PoInputGrid({ | |||||
// setBtnIsLoading(false); | // setBtnIsLoading(false); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[], | |||||
[openPutAwayModal], | |||||
); | ); | ||||
const printQrcode = useCallback( | const printQrcode = useCallback( | ||||
@@ -310,79 +346,47 @@ function PoInputGrid({ | |||||
} | } | ||||
setBtnIsLoading(false); | 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[]>( | const columns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
{ | { | ||||
field: "itemNo", | field: "itemNo", | ||||
headerName: t("itemNo"), | headerName: t("itemNo"), | ||||
flex: 0.4, | |||||
width: 120, | |||||
// flex: 0.4, | |||||
}, | }, | ||||
{ | { | ||||
field: "itemName", | field: "itemName", | ||||
headerName: t("itemName"), | headerName: t("itemName"), | ||||
flex: 0.6, | |||||
width: 120, | |||||
// flex: 0.6, | |||||
}, | }, | ||||
{ | { | ||||
field: "acceptedQty", | field: "acceptedQty", | ||||
headerName: t("acceptedQty"), | headerName: t("acceptedQty"), | ||||
flex: 0.5, | |||||
// flex: 0.5, | |||||
width: 120, | |||||
type: "number", | type: "number", | ||||
// editable: true, | // editable: true, | ||||
// replace with tooltip + content | // replace with tooltip + content | ||||
@@ -390,7 +394,8 @@ function PoInputGrid({ | |||||
{ | { | ||||
field: "uom", | field: "uom", | ||||
headerName: t("uom"), | headerName: t("uom"), | ||||
flex: 0.5, | |||||
width: 120, | |||||
// flex: 0.5, | |||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return params.row.uom.code; | return params.row.uom.code; | ||||
}, | }, | ||||
@@ -398,7 +403,8 @@ function PoInputGrid({ | |||||
{ | { | ||||
field: "weight", | field: "weight", | ||||
headerName: t("weight"), | headerName: t("weight"), | ||||
flex: 0.5, | |||||
width: 120, | |||||
// flex: 0.5, | |||||
renderCell: (params) => { | renderCell: (params) => { | ||||
const weight = calculateWeight( | const weight = calculateWeight( | ||||
params.row.acceptedQty, | params.row.acceptedQty, | ||||
@@ -411,7 +417,8 @@ function PoInputGrid({ | |||||
{ | { | ||||
field: "status", | field: "status", | ||||
headerName: t("status"), | headerName: t("status"), | ||||
flex: 0.5, | |||||
width: 120, | |||||
// flex: 0.5, | |||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return t(`${params.row.status}`); | return t(`${params.row.status}`); | ||||
}, | }, | ||||
@@ -423,7 +430,8 @@ function PoInputGrid({ | |||||
"stock in", | "stock in", | ||||
)} | ${t("putaway")} | ${t("delete")}`, | )} | ${t("putaway")} | ${t("delete")}`, | ||||
// headerName: "start | qc | escalation | stock in | putaway | delete", | // headerName: "start | qc | escalation | stock in | putaway | delete", | ||||
flex: 1.5, | |||||
width: 300, | |||||
// flex: 1.5, | |||||
cellClassName: "actions", | cellClassName: "actions", | ||||
getActions: (params) => { | getActions: (params) => { | ||||
// console.log(params.row.status); | // console.log(params.row.status); | ||||
@@ -494,7 +502,7 @@ function PoInputGrid({ | |||||
(stockInLineStatusMap[status] >= 3 && | (stockInLineStatusMap[status] >= 3 && | ||||
stockInLineStatusMap[status] <= 5 && | stockInLineStatusMap[status] <= 5 && | ||||
!session?.user?.abilities?.includes("APPROVAL")) | !session?.user?.abilities?.includes("APPROVAL")) | ||||
} | |||||
} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleStockIn(params.row.id, params)} | 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(() => { | const addRow = useCallback(() => { | ||||
@@ -585,7 +593,7 @@ function PoInputGrid({ | |||||
// fieldToFocus: "projectId", | // fieldToFocus: "projectId", | ||||
}, | }, | ||||
})); | })); | ||||
}, [currQty, getRowId]); | |||||
}, [currQty, getRowId, itemDetail]); | |||||
const validation = useCallback( | const validation = useCallback( | ||||
( | ( | ||||
newRow: GridRowModel<StockInLineRow>, | newRow: GridRowModel<StockInLineRow>, | ||||
@@ -599,7 +607,7 @@ function PoInputGrid({ | |||||
} | } | ||||
return Object.keys(error).length > 0 ? error : undefined; | return Object.keys(error).length > 0 ? error : undefined; | ||||
}, | }, | ||||
[currQty], | |||||
[currQty, itemDetail.qty, t], | |||||
); | ); | ||||
const processRowUpdate = useCallback( | const processRowUpdate = useCallback( | ||||
( | ( | ||||
@@ -632,7 +640,7 @@ function PoInputGrid({ | |||||
setCurrQty(total); | setCurrQty(total); | ||||
return rowToSave; | return rowToSave; | ||||
}, | }, | ||||
[getRowId, entries], | |||||
[validation, entries, setStockInLine, getRowId], | |||||
); | ); | ||||
const onProcessRowUpdateError = useCallback( | const onProcessRowUpdateError = useCallback( | ||||
@@ -40,6 +40,8 @@ import { fetchPoQrcode } from "@/app/api/pdf/actions"; | |||||
import UploadContext from "../UploadProvider/UploadProvider"; | import UploadContext from "../UploadProvider/UploadProvider"; | ||||
import useUploadContext from "../UploadProvider/useUploadContext"; | import useUploadContext from "../UploadProvider/useUploadContext"; | ||||
import RejectForm from "./RejectForm"; | import RejectForm from "./RejectForm"; | ||||
import { isNullOrUndefined } from "html5-qrcode/esm/core"; | |||||
import { isEmpty, isFinite } from "lodash"; | |||||
dayjs.extend(arraySupport); | dayjs.extend(arraySupport); | ||||
interface CommonProps extends Omit<ModalProps, "children"> { | interface CommonProps extends Omit<ModalProps, "children"> { | ||||
@@ -153,9 +155,37 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
// } | // } | ||||
// return date; | // return date; | ||||
// }, []); | // }, []); | ||||
const accQty = formProps.watch("acceptedQty"); | |||||
const productLotNo = formProps.watch("productLotNo"); | |||||
const checkStockIn = useCallback( | const checkStockIn = useCallback( | ||||
(data: ModalFormInput): boolean => { | (data: ModalFormInput): boolean => { | ||||
let hasErrors = false; | 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) { | if (itemDetail.shelfLife && !data.productionDate && !data.expiryDate) { | ||||
formProps.setError("productionDate", { | formProps.setError("productionDate", { | ||||
message: "Please provide at least one", | message: "Please provide at least one", | ||||
@@ -184,7 +214,7 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
} | } | ||||
return hasErrors; | return hasErrors; | ||||
}, | }, | ||||
[itemDetail, formProps], | |||||
[accQty, itemDetail.acceptedQty, itemDetail.shelfLife, productLotNo, formProps, t], | |||||
); | ); | ||||
const checkPutaway = useCallback( | const checkPutaway = useCallback( | ||||
@@ -97,7 +97,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
useEffect(() => { | useEffect(() => { | ||||
clearErrors(); | clearErrors(); | ||||
validateForm(); | validateForm(); | ||||
}, [validateForm]); | |||||
}, [clearErrors, validateForm]); | |||||
const columns = useMemo<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
@@ -30,7 +30,7 @@ const QcSelect: React.FC<Props> = ({ allQcs, value, error, onQcSelect }) => { | |||||
const filteredQc = useMemo(() => { | const filteredQc = useMemo(() => { | ||||
// do filtering here if any | // do filtering here if any | ||||
return allQcs; | return allQcs; | ||||
}, []); | |||||
}, [allQcs]); | |||||
const options = useMemo(() => { | const options = useMemo(() => { | ||||
return [ | return [ | ||||
{ | { | ||||
@@ -44,7 +44,7 @@ const QcSelect: React.FC<Props> = ({ allQcs, value, error, onQcSelect }) => { | |||||
group: "existing", | group: "existing", | ||||
})), | })), | ||||
]; | ]; | ||||
}, [filteredQc]); | |||||
}, [t, filteredQc]); | |||||
const currentValue = options.find((o) => o.value === value) || options[0]; | const currentValue = options.find((o) => o.value === value) || options[0]; | ||||
@@ -96,7 +96,8 @@ const StockInForm: React.FC<Props> = ({ | |||||
console.log(expiryDate); | console.log(expiryDate); | ||||
if (expiryDate) clearErrors(); | if (expiryDate) clearErrors(); | ||||
if (productionDate) clearErrors(); | if (productionDate) clearErrors(); | ||||
}, [productionDate, expiryDate]); | |||||
}, [productionDate, expiryDate, clearErrors]); | |||||
return ( | return ( | ||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
@@ -111,6 +112,30 @@ const StockInForm: React.FC<Props> = ({ | |||||
spacing={2} | spacing={2} | ||||
sx={{ mt: 0.5 }} | 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}> | <Grid item xs={4}> | ||||
<TextField | <TextField | ||||
label={t("productLotNo")} | label={t("productLotNo")} | ||||
@@ -119,8 +144,8 @@ const StockInForm: React.FC<Props> = ({ | |||||
// required: "productLotNo required!", | // required: "productLotNo required!", | ||||
})} | })} | ||||
disabled={disabled} | disabled={disabled} | ||||
// error={Boolean(errors.productLotNo)} | |||||
// helperText={errors.productLotNo?.message} | |||||
error={Boolean(errors.productLotNo)} | |||||
helperText={errors.productLotNo?.message} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | <Grid item xs={4}> | ||||
@@ -171,7 +196,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | |||||
{/* <Grid item xs={4}> | |||||
<TextField | <TextField | ||||
label={t("acceptedWeight")} | label={t("acceptedWeight")} | ||||
fullWidth | fullWidth | ||||
@@ -182,7 +207,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
error={Boolean(errors.acceptedWeight)} | error={Boolean(errors.acceptedWeight)} | ||||
helperText={errors.acceptedWeight?.message} | helperText={errors.acceptedWeight?.message} | ||||
/> | /> | ||||
</Grid> | |||||
</Grid> */} | |||||
<Grid item xs={4}> | <Grid item xs={4}> | ||||
<Controller | <Controller | ||||
control={control} | control={control} | ||||
@@ -248,7 +248,7 @@ function SearchBox<T extends string>({ | |||||
<MenuItem value={"All"}>{t("All")}</MenuItem> | <MenuItem value={"All"}>{t("All")}</MenuItem> | ||||
{c.options.map((option) => ( | {c.options.map((option) => ( | ||||
<MenuItem key={option} value={option}> | <MenuItem key={option} value={option}> | ||||
{option} | |||||
{t(option)} | |||||
</MenuItem> | </MenuItem> | ||||
))} | ))} | ||||
</Select> | </Select> | ||||
@@ -100,6 +100,7 @@ interface Props<T extends ResultWithId> { | |||||
isAutoPaging?: boolean; | isAutoPaging?: boolean; | ||||
checkboxIds?: (string | number)[]; | checkboxIds?: (string | number)[]; | ||||
setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>; | setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>; | ||||
onRowClick?: (item: T) => void; | |||||
} | } | ||||
function isActionColumn<T extends ResultWithId>( | function isActionColumn<T extends ResultWithId>( | ||||
@@ -138,8 +139,8 @@ function convertObjectKeysToLowercase<T extends object>( | |||||
): object | undefined { | ): object | undefined { | ||||
return obj | return obj | ||||
? Object.fromEntries( | ? Object.fromEntries( | ||||
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]), | |||||
) | |||||
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]), | |||||
) | |||||
: undefined; | : undefined; | ||||
} | } | ||||
@@ -174,6 +175,14 @@ export const defaultPagingController: { pageNum: number; pageSize: number } = { | |||||
pageNum: 1, | pageNum: 1, | ||||
pageSize: 10, | pageSize: 10, | ||||
}; | }; | ||||
export type defaultSetPagingController = Dispatch< | |||||
SetStateAction<{ | |||||
pageNum: number; | |||||
pageSize: number; | |||||
}> | |||||
> | |||||
function SearchResults<T extends ResultWithId>({ | function SearchResults<T extends ResultWithId>({ | ||||
items, | items, | ||||
columns, | columns, | ||||
@@ -184,6 +193,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
totalCount, | totalCount, | ||||
checkboxIds = [], | checkboxIds = [], | ||||
setCheckboxIds = undefined, | setCheckboxIds = undefined, | ||||
onRowClick = undefined, | |||||
}: Props<T>) { | }: Props<T>) { | ||||
const [page, setPage] = React.useState(0); | const [page, setPage] = React.useState(0); | ||||
const [rowsPerPage, setRowsPerPage] = React.useState(10); | const [rowsPerPage, setRowsPerPage] = React.useState(10); | ||||
@@ -279,40 +289,25 @@ function SearchResults<T extends ResultWithId>({ | |||||
<TableBody> | <TableBody> | ||||
{isAutoPaging | {isAutoPaging | ||||
? items | ? 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 | setCheckboxIds | ||||
? (event) => handleRowClick(event, item, columns) | |||||
? handleRowClick(event, item, columns) | |||||
: undefined | : 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) => { | {columns.map((column, idx) => { | ||||
const columnName = column.name; | const columnName = column.name; | ||||
@@ -329,7 +324,27 @@ function SearchResults<T extends ResultWithId>({ | |||||
})} | })} | ||||
</TableRow> | </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> | </TableBody> | ||||
</Table> | </Table> | ||||
</TableContainer> | </TableContainer> | ||||
@@ -35,6 +35,7 @@ | |||||
"Project":"專案", | "Project":"專案", | ||||
"Product":"產品", | "Product":"產品", | ||||
"Material":"材料", | "Material":"材料", | ||||
"mat":"原料", | |||||
"FG":"成品", | "FG":"成品", | ||||
"FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", | "FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", | ||||
"View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", | "View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", | ||||
@@ -7,5 +7,13 @@ | |||||
"Qty": "數量", | "Qty": "數量", | ||||
"UoM": "單位", | "UoM": "單位", | ||||
"mat": "物料", | "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": "已選擇項目" | |||||
} |
@@ -27,7 +27,7 @@ | |||||
"total weight": "總重量", | "total weight": "總重量", | ||||
"weight unit": "重量單位", | "weight unit": "重量單位", | ||||
"price": "價格", | "price": "價格", | ||||
"processed": "已入倉", | |||||
"processed": "已處理", | |||||
"expiryDate": "到期日", | "expiryDate": "到期日", | ||||
"acceptedQty": "接受數量", | "acceptedQty": "接受數量", | ||||
"weight": "重量", | "weight": "重量", | ||||