# Conflicts: # src/app/api/pickOrder/index.ts # src/components/PickOrderSearch/PickOrderSearch.tsxmaster
| @@ -55,8 +55,8 @@ export default async function MainLayout({ | |||||
| <Stack spacing={2}> | <Stack spacing={2}> | ||||
| <I18nProvider namespaces={["common"]}> | <I18nProvider namespaces={["common"]}> | ||||
| <Breadcrumb /> | <Breadcrumb /> | ||||
| {children} | |||||
| </I18nProvider> | </I18nProvider> | ||||
| {children} | |||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| </> | </> | ||||
| @@ -0,0 +1,30 @@ | |||||
| import { PreloadPickOrder } from "@/app/api/pickorder"; | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import PickOrderDetail from "@/components/PickOrderDetail"; | |||||
| import { getServerI18n, I18nProvider } from "@/i18n"; | |||||
| import { Stack, Typography } from "@mui/material"; | |||||
| import { Metadata } from "next"; | |||||
| import { Suspense } from "react"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Consolidated Pick Order Flow", | |||||
| }; | |||||
| type Props = {} & SearchParams; | |||||
| const PickOrder: React.FC<Props> = async ({ searchParams }) => { | |||||
| const { t } = await getServerI18n("pickOrder"); | |||||
| PreloadPickOrder(); | |||||
| return ( | |||||
| <> | |||||
| <I18nProvider namespaces={["pickOrder"]}> | |||||
| <Suspense fallback={<PickOrderDetail.Loading />}> | |||||
| <PickOrderDetail consoCode={`${searchParams["consoCode"]}`}/> | |||||
| </Suspense> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PickOrder; | |||||
| @@ -1,4 +1,4 @@ | |||||
| import { PreloadPickOrder } from "@/app/api/pickOrder"; | |||||
| import { PreloadPickOrder } from "@/app/api/pickorder"; | |||||
| import PickOrderSearch from "@/components/PickOrderSearch"; | import PickOrderSearch from "@/components/PickOrderSearch"; | ||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
| @@ -0,0 +1,23 @@ | |||||
| "use server"; | |||||
| 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 { QcItemResult } from "../settings/qcItem"; | |||||
| import { RecordsRes } from "../utils"; | |||||
| // import { BASE_API_URL } from "@/config/api"; | |||||
| export interface LotLineInfo { | |||||
| inventoryLotLineId: number, | |||||
| lotNo: string, | |||||
| remainingQty: number, | |||||
| uom: string | |||||
| } | |||||
| export const fetchLotDetail = cache(async (stockInLineId: number) => { | |||||
| return serverFetchJson<LotLineInfo>(`${BASE_API_URL}/inventoryLotLine/lot-detail/${stockInLineId}`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["inventory"] }, | |||||
| }); | |||||
| }); | |||||
| @@ -0,0 +1,101 @@ | |||||
| "use server"; | |||||
| 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 { QcItemResult } from "../settings/qcItem"; | |||||
| import { RecordsRes } from "../utils"; | |||||
| import { ConsoPickOrderResult, PickOrderLineWithSuggestedLot, PickOrderResult, PreReleasePickOrderSummary } from "."; | |||||
| // import { BASE_API_URL } from "@/config/api"; | |||||
| export interface ReleasePickOrderInputs { | |||||
| consoCode: string | |||||
| assignTo: number, | |||||
| } | |||||
| export const consolidatePickOrder = async (ids: number[]) => { | |||||
| const pickOrder = await serverFetchJson<any>(`${BASE_API_URL}/pickOrder/conso`, { | |||||
| method: "POST", | |||||
| body: JSON.stringify({ ids: ids }), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| // revalidateTag("po"); | |||||
| return pickOrder | |||||
| } | |||||
| export const consolidatePickOrder_revert = async (ids: number[]) => { | |||||
| const pickOrder = await serverFetchJson<any>(`${BASE_API_URL}/pickOrder/deconso`, { | |||||
| method: "POST", | |||||
| body: JSON.stringify({ ids: ids }), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| // revalidateTag("po"); | |||||
| return pickOrder | |||||
| } | |||||
| export const fetchPickOrderClient = cache(async (queryParams?: Record<string, any>) => { | |||||
| if (queryParams) { | |||||
| const queryString = new URLSearchParams(queryParams).toString(); | |||||
| return serverFetchJson<RecordsRes<PickOrderResult[]>>(`${BASE_API_URL}/pickOrder/getRecordByPage?${queryString}`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| }); | |||||
| } else { | |||||
| return serverFetchJson<RecordsRes<PickOrderResult[]>>(`${BASE_API_URL}/pickOrder/getRecordByPage`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| export const fetchConsoPickOrderClient = cache(async (queryParams?: Record<string, any>) => { | |||||
| if (queryParams) { | |||||
| const queryString = new URLSearchParams(queryParams).toString(); | |||||
| return serverFetchJson<RecordsRes<ConsoPickOrderResult[]>>(`${BASE_API_URL}/pickOrder/getRecordByPage-conso?${queryString}`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| }); | |||||
| } else { | |||||
| return serverFetchJson<RecordsRes<ConsoPickOrderResult[]>>(`${BASE_API_URL}/pickOrder/getRecordByPage-conso`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| export const fetchPickOrderLineClient = cache(async (queryParams?: Record<string, any>) => { | |||||
| if (queryParams) { | |||||
| const queryString = new URLSearchParams(queryParams).toString(); | |||||
| return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(`${BASE_API_URL}/pickOrder/get-pickorder-line-byPage?${queryString}`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| }); | |||||
| } else { | |||||
| return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(`${BASE_API_URL}/pickOrder/get-pickorder-line-byPage`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| export const fetchConsoDetail = cache(async (consoCode: string) => { | |||||
| return serverFetchJson<PreReleasePickOrderSummary>(`${BASE_API_URL}/pickOrder/pre-release-info/${consoCode}`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| }); | |||||
| }); | |||||
| export const releasePickOrder = async (data: ReleasePickOrderInputs) => { | |||||
| console.log(data) | |||||
| console.log(JSON.stringify(data)) | |||||
| const po = await serverFetchJson<any>(`${BASE_API_URL}/pickOrder/releaseConso`, { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| revalidateTag("pickorder"); | |||||
| return po | |||||
| } | |||||
| @@ -1,8 +1,6 @@ | |||||
| import "server-only"; | import "server-only"; | ||||
| // import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| // import { BASE_API_URL } from "@/config/api"; | |||||
| import { serverFetchJson } from "../../utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "../../../config/api"; | |||||
| import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| import { cache } from "react"; | import { cache } from "react"; | ||||
| interface PickOrderItemInfo { | interface PickOrderItemInfo { | ||||
| @@ -20,14 +18,77 @@ export interface PickOrderResult{ | |||||
| status: string, | status: string, | ||||
| releasedBy: string, | releasedBy: string, | ||||
| items?: PickOrderItemInfo[] | null, | items?: PickOrderItemInfo[] | null, | ||||
| pickOrderLine?: PickOrderLine[] | |||||
| } | |||||
| export interface PickOrderLine { | |||||
| id: number, | |||||
| itemId: number, | |||||
| itemCode: string, | |||||
| itemName: string, | |||||
| availableQty: number, | |||||
| requiredQty: number, | |||||
| uomCode: string, | |||||
| uomDesc: string | |||||
| } | |||||
| export interface ConsoPickOrderResult{ | |||||
| id: number, | |||||
| code: string, | |||||
| consoCode?: string, | |||||
| targetDate: number[], | |||||
| completeDate?: number[], | |||||
| type: string, | |||||
| status: string, | |||||
| releasedBy: string, | |||||
| items?: PickOrderItemInfo[] | null, | |||||
| } | |||||
| export interface FetchPickOrders extends Pageable { | |||||
| code: string | undefined | |||||
| targetDateFrom: string | undefined | |||||
| targetDateTo: string | undefined | |||||
| type: string | undefined | |||||
| status: string | undefined | |||||
| itemName: string | undefined | |||||
| } | |||||
| export type ByItemsSummary = { | |||||
| id: number, | |||||
| code: string, | |||||
| name: string, | |||||
| uomDesc: string, | |||||
| availableQty: number, | |||||
| requiredQty: number, | |||||
| } | |||||
| export interface PreReleasePickOrderSummary { | |||||
| consoCode: string | |||||
| pickOrders: Omit<PickOrderResult, "items">[] | |||||
| items: ByItemsSummary[] | |||||
| } | |||||
| export interface PickOrderLineWithSuggestedLot { | |||||
| id: number, | |||||
| itemName: string, | |||||
| qty: number, | |||||
| uom: string | |||||
| status: string | |||||
| warehouse: string | |||||
| suggestedLotNo: string | |||||
| } | } | ||||
| export const PreloadPickOrder = () => { | export const PreloadPickOrder = () => { | ||||
| fetchPickOrders() | |||||
| fetchPickOrders({ | |||||
| code: undefined, | |||||
| targetDateFrom: undefined, | |||||
| targetDateTo: undefined, | |||||
| type: undefined, | |||||
| status: undefined, | |||||
| itemName: undefined, | |||||
| }) | |||||
| } | } | ||||
| export const fetchPickOrders = cache(async () => { | |||||
| return serverFetchJson<PickOrderResult[]>(`${BASE_API_URL}/pickOrder/list`, { | |||||
| export const fetchPickOrders = cache(async (queryParams: FetchPickOrders) => { | |||||
| const queryString = new URLSearchParams(queryParams as Record<string, any>).toString(); | |||||
| return serverFetchJson<PickOrderResult[]>(`${BASE_API_URL}/pickOrder/list?${queryString}`, { | |||||
| next: { | next: { | ||||
| tags: ["pickOrders"] | tags: ["pickOrders"] | ||||
| } | } | ||||
| @@ -6,8 +6,10 @@ import { serverFetchJson } from "../../utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "../../../config/api"; | import { BASE_API_URL } from "../../../config/api"; | ||||
| export interface QrCodeInfo { | export interface QrCodeInfo { | ||||
| stockInLineId?: number; | |||||
| itemId: number | |||||
| warehouseId?: number | |||||
| lotNo?: string | |||||
| } | |||||
| // warehouse qrcode | |||||
| warehouseId?: number | |||||
| // item qrcode | |||||
| stockInLineId?: number; | |||||
| itemId: number | |||||
| lotNo?: string | |||||
| } | |||||
| @@ -22,12 +22,23 @@ export interface PasswordInputs { | |||||
| newPasswordCheck: string; | newPasswordCheck: string; | ||||
| } | } | ||||
| export interface NameList { | |||||
| id: number | |||||
| name: string | |||||
| } | |||||
| export const fetchUserDetails = cache(async (id: number) => { | export const fetchUserDetails = cache(async (id: number) => { | ||||
| return serverFetchJson<UserDetail>(`${BASE_API_URL}/user/${id}`, { | return serverFetchJson<UserDetail>(`${BASE_API_URL}/user/${id}`, { | ||||
| next: { tags: ["user"] }, | next: { tags: ["user"] }, | ||||
| }); | }); | ||||
| }); | }); | ||||
| export const fetchNameList = cache(async () => { | |||||
| return serverFetchJson<NameList[]>(`${BASE_API_URL}/user/name-list`, { | |||||
| next: { tags: ["user"] }, | |||||
| }); | |||||
| }); | |||||
| export const editUser = async (id: number, data: UserInputs) => { | export const editUser = async (id: number, data: UserInputs) => { | ||||
| const newUser = serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, { | const newUser = serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, { | ||||
| method: "PUT", | method: "PUT", | ||||
| @@ -3,6 +3,11 @@ import { getServerSession } from "next-auth"; | |||||
| import { headers } from "next/headers"; | import { headers } from "next/headers"; | ||||
| import { redirect } from "next/navigation"; | import { redirect } from "next/navigation"; | ||||
| export interface Pageable { | |||||
| pageSize?: number | |||||
| pageNum?: number | |||||
| } | |||||
| export type SearchParams = { | export type SearchParams = { | ||||
| searchParams: { [key: string]: string | string[] | undefined }; | searchParams: { [key: string]: string | string[] | undefined }; | ||||
| } | } | ||||
| @@ -67,6 +67,13 @@ export const stockInLineStatusMap: { [status: string]: number } = { | |||||
| "rejected": 9, | "rejected": 9, | ||||
| }; | }; | ||||
| export const pickOrderStatusMap: { [status: string]: number } = { | |||||
| "pending": 1, | |||||
| "consolidated": 2, | |||||
| "released": 3, | |||||
| "completed": 4, | |||||
| }; | |||||
| export const calculateWeight = (qty: number, uom: Uom) => { | export const calculateWeight = (qty: number, uom: Uom) => { | ||||
| return qty * (uom.unit2Qty || 1) * (uom.unit3Qty || 1) * (uom.unit4Qty || 1); | return qty * (uom.unit2Qty || 1) * (uom.unit3Qty || 1) * (uom.unit4Qty || 1); | ||||
| } | } | ||||
| @@ -24,6 +24,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| "/do": "Delivery Order", | "/do": "Delivery Order", | ||||
| "/pickOrder": "Pick Order", | "/pickOrder": "Pick Order", | ||||
| "/po": "Purchase Order", | "/po": "Purchase Order", | ||||
| "/dashboard": "dashboard", | |||||
| }; | }; | ||||
| const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
| @@ -52,7 +52,7 @@ const NavigationContent: React.FC = () => { | |||||
| { | { | ||||
| icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
| label: "Pick Order", | label: "Pick Order", | ||||
| path: "/pickOrder", | |||||
| path: "/pickorder", | |||||
| }, | }, | ||||
| // { | // { | ||||
| // icon: <RequestQuote />, | // icon: <RequestQuote />, | ||||
| @@ -0,0 +1,314 @@ | |||||
| "use client"; | |||||
| import { | |||||
| Button, | |||||
| ButtonProps, | |||||
| Card, | |||||
| CardContent, | |||||
| CardHeader, | |||||
| CircularProgress, | |||||
| Grid, | |||||
| Stack, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import { GridColDef } from "@mui/x-data-grid"; | |||||
| import { PlayArrow } from "@mui/icons-material"; | |||||
| import DoneIcon from "@mui/icons-material/Done"; | |||||
| import { GridRowSelectionModel } from "@mui/x-data-grid"; | |||||
| import { useQcCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | |||||
| import { fetchPickOrderLineClient } from "@/app/api/pickorder/actions"; | |||||
| import { PickOrderLineWithSuggestedLot } from "@/app/api/pickorder"; | |||||
| import { Pageable } from "@/app/utils/fetchUtil"; | |||||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||||
| import { QrCode } from "../QrCode"; | |||||
| import { fetchLotDetail, LotLineInfo } from "@/app/api/inventory/actions"; | |||||
| import { GridRowModesModel } from "@mui/x-data-grid"; | |||||
| interface Props { | |||||
| consoCode: string; | |||||
| } | |||||
| interface IsLoadingModel { | |||||
| pickOrderLineTable: boolean; | |||||
| stockOutLineTable: boolean; | |||||
| } | |||||
| const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const [selectedRow, setSelectRow] = useState<GridRowSelectionModel>(); | |||||
| const [isLoadingModel, setIsLoadingModel] = useState<IsLoadingModel>({ | |||||
| pickOrderLineTable: false, | |||||
| stockOutLineTable: false, | |||||
| }); | |||||
| const [polCriteriaArgs, setPolCriteriaArgs] = useState<Pageable>({ | |||||
| pageNum: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const [solCriteriaArgs, setSolCriteriaArgs] = useState<Pageable>({ | |||||
| pageNum: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const [polTotalCount, setPolTotalCount] = useState(0); | |||||
| const [solTotalCount, setSolTotalCount] = useState(0); | |||||
| const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||||
| const [pickOrderLine, setPickOrderLine] = useState< | |||||
| PickOrderLineWithSuggestedLot[] | |||||
| >([]); | |||||
| const pickOrderLineColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "id", | |||||
| headerName: "pickOrderLineId", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "itemName", | |||||
| headerName: "itemId", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "qty", | |||||
| headerName: "qty", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "uom", | |||||
| headerName: "uom", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "warehouse", | |||||
| headerName: "location", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "suggestedLotNo", | |||||
| headerName: "suggestedLotNo", | |||||
| flex: 1.2, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const [stockOutLine, setStockOutLine] = useState([]); | |||||
| const stockOutLineColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "code", | |||||
| headerName: "actual lot (out line", | |||||
| flex: 1, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const handleStartPickOrder = useCallback(async () => {}, []); | |||||
| const handleCompletePickOrder = useCallback(async () => {}, []); | |||||
| useEffect(() => { | |||||
| console.log(selectedRow); | |||||
| }, [selectedRow]); | |||||
| const buttonData = useMemo( | |||||
| () => ({ | |||||
| buttonName: "complete", | |||||
| title: t("Do you want to complete?"), | |||||
| confirmButtonText: t("Complete"), | |||||
| successTitle: t("Complete Success"), | |||||
| errorTitle: t("Complete Fail"), | |||||
| buttonText: t("Complete PO"), | |||||
| buttonIcon: <DoneIcon />, | |||||
| buttonColor: "info", | |||||
| disabled: true, | |||||
| }), | |||||
| [] | |||||
| ); | |||||
| const [isOpenScanner, setOpenScanner] = useState(false); | |||||
| const onOpenScanner = useCallback(() => { | |||||
| setOpenScanner((prev) => !prev); | |||||
| }, []); | |||||
| const fetchPickOrderLine = useCallback( | |||||
| async (params: Record<string, any>) => { | |||||
| setIsLoadingModel((prev) => ({ | |||||
| ...prev, | |||||
| pickOrderLineTable: true, | |||||
| })); | |||||
| const res = await fetchPickOrderLineClient({ | |||||
| ...params, | |||||
| consoCode: consoCode, | |||||
| }); | |||||
| if (res) { | |||||
| console.log(res); | |||||
| setPickOrderLine(res.records); | |||||
| setPolTotalCount(res.total); | |||||
| } else { | |||||
| console.log("error"); | |||||
| console.log(res); | |||||
| } | |||||
| setIsLoadingModel((prev) => ({ | |||||
| ...prev, | |||||
| pickOrderLineTable: false, | |||||
| })); | |||||
| }, | |||||
| [fetchPickOrderLineClient, consoCode] | |||||
| ); | |||||
| const fetchStockOutLine = useCallback( | |||||
| async (params: Record<string, any>) => {}, | |||||
| [] | |||||
| ); | |||||
| useEffect(() => { | |||||
| fetchPickOrderLine(polCriteriaArgs); | |||||
| }, [polCriteriaArgs]); | |||||
| useEffect(() => { | |||||
| fetchStockOutLine(solCriteriaArgs); | |||||
| }, [solCriteriaArgs]); | |||||
| const getLotDetail = useCallback( | |||||
| async (stockInLineId: number): Promise<LotLineInfo> => { | |||||
| const res = await fetchLotDetail(stockInLineId); | |||||
| return res; | |||||
| }, | |||||
| [fetchLotDetail] | |||||
| ); | |||||
| const scanner = useQcCodeScanner(); | |||||
| useEffect(() => { | |||||
| if (isOpenScanner && !scanner.isScanning) { | |||||
| scanner.startScan(); | |||||
| } else if (!isOpenScanner && scanner.isScanning) { | |||||
| scanner.stopScan(); | |||||
| } | |||||
| }, [isOpenScanner]); | |||||
| useEffect(() => { | |||||
| if (scanner.values.length > 0) { | |||||
| console.log(scanner.values[0]); | |||||
| const data: QrCodeInfo = JSON.parse(scanner.values[0]); | |||||
| console.log(data); | |||||
| if (data.stockInLineId) { | |||||
| console.log("still got in"); | |||||
| console.log(data.stockInLineId); | |||||
| // fetch | |||||
| getLotDetail(data.stockInLineId).then((value) => {}); | |||||
| } | |||||
| scanner.resetScan(); | |||||
| } | |||||
| }, [scanner.values]); | |||||
| const homemade_Qrcode = { | |||||
| stockInLineId: 156, | |||||
| }; | |||||
| return ( | |||||
| <> | |||||
| <Stack spacing={2}> | |||||
| <Grid container xs={12} justifyContent="start"> | |||||
| <Grid item xs={12}> | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {consoCode} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={8}> | |||||
| <Button | |||||
| // onClick={buttonData.onClick} | |||||
| disabled={buttonData.disabled} | |||||
| color={buttonData.buttonColor as ButtonProps["color"]} | |||||
| startIcon={buttonData.buttonIcon} | |||||
| > | |||||
| {buttonData.buttonText} | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid | |||||
| item | |||||
| xs={4} | |||||
| display="flex" | |||||
| justifyContent="end" | |||||
| alignItems="end" | |||||
| > | |||||
| <Button onClick={onOpenScanner}> | |||||
| {isOpenScanner ? t("binding") : t("bind")} | |||||
| </Button> | |||||
| </Grid> | |||||
| {/* homemade qrcode for testing purpose */} | |||||
| {/* <Grid | |||||
| item | |||||
| xs={12} | |||||
| style={{ display: "flex", justifyContent: "center" }} | |||||
| > | |||||
| <QrCode | |||||
| content={homemade_Qrcode} | |||||
| sx={{ width: 200, height: 200 }} | |||||
| /> | |||||
| </Grid> */} | |||||
| </Grid> | |||||
| <Grid container xs={12} justifyContent="space-between"> | |||||
| {/* <Grid item xs={12} sx={{ height: 400 }}> | |||||
| <StyledDataGrid rows={pickOrderLine} columns={columns} /> | |||||
| </Grid> */} | |||||
| <Grid item xs={12} sx={{ height: 400 }}> | |||||
| {isLoadingModel.pickOrderLineTable ? ( | |||||
| <CircularProgress size={40} /> | |||||
| ) : ( | |||||
| <StyledDataGrid | |||||
| rows={pickOrderLine} | |||||
| columns={pickOrderLineColumns} | |||||
| rowSelectionModel={selectedRow} | |||||
| onRowSelectionModelChange={(newRowSelectionModel) => { | |||||
| setSelectRow(newRowSelectionModel); | |||||
| }} | |||||
| initialState={{ | |||||
| pagination: { | |||||
| paginationModel: { pageSize: 10, page: 0 }, | |||||
| }, | |||||
| }} | |||||
| pageSizeOptions={[10, 25, 50, 100]} | |||||
| onPaginationModelChange={async (model, details) => { | |||||
| setPolCriteriaArgs({ | |||||
| pageNum: model.page + 1, | |||||
| pageSize: model.pageSize, | |||||
| }); | |||||
| }} | |||||
| rowCount={polTotalCount} | |||||
| /> | |||||
| )} | |||||
| </Grid> | |||||
| <Grid item xs={12} sx={{ height: 400 }}> | |||||
| <StyledDataGrid | |||||
| rows={stockOutLine} | |||||
| columns={stockOutLineColumns} | |||||
| rowModesModel={rowModesModel} | |||||
| onRowModesModelChange={setRowModesModel} | |||||
| disableColumnMenu | |||||
| editMode="row" | |||||
| // processRowUpdate={processRowUpdate} | |||||
| // onProcessRowUpdateError={onProcessRowUpdateError} | |||||
| initialState={{ | |||||
| pagination: { | |||||
| paginationModel: { pageSize: 10, page: 0 }, | |||||
| }, | |||||
| }} | |||||
| pageSizeOptions={[10, 25, 50, 100]} | |||||
| onPaginationModelChange={async (model, details) => { | |||||
| setSolCriteriaArgs({ | |||||
| pageNum: model.page + 1, | |||||
| pageSize: model.pageSize, | |||||
| }); | |||||
| }} | |||||
| rowCount={solTotalCount} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Stack> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PickOrderDetail; | |||||
| @@ -0,0 +1,40 @@ | |||||
| import Card from "@mui/material/Card"; | |||||
| import CardContent from "@mui/material/CardContent"; | |||||
| import Skeleton from "@mui/material/Skeleton"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import React from "react"; | |||||
| // Can make this nicer | |||||
| export const PickOrderDetailLoading: React.FC = () => { | |||||
| return ( | |||||
| <> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton | |||||
| variant="rounded" | |||||
| height={50} | |||||
| width={100} | |||||
| sx={{ alignSelf: "flex-end" }} | |||||
| /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PickOrderDetailLoading; | |||||
| @@ -0,0 +1,35 @@ | |||||
| import { fetchAllItems } from "@/app/api/settings/item"; | |||||
| // import ItemsSearch from "./ItemsSearch"; | |||||
| // import ItemsSearchLoading from "./ItemsSearchLoading"; | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import { notFound } from "next/navigation"; | |||||
| import { fetchPoWithStockInLines, PoResult } from "@/app/api/po"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { fetchWarehouseList } from "@/app/api/warehouse"; | |||||
| import { fetchQcItemCheck } from "@/app/api/qc/actions"; | |||||
| import PickOrderDetail from "./PickOrderDetail"; | |||||
| import PickOrderDetailLoading from "./PickOrderDetailLoading"; | |||||
| interface SubComponents { | |||||
| Loading: typeof PickOrderDetailLoading; | |||||
| } | |||||
| type Props = { | |||||
| consoCode: string; | |||||
| }; | |||||
| const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ consoCode }) => { | |||||
| // const [poWithStockInLine, warehouse, qc] = await Promise.all([ | |||||
| // fetchPoWithStockInLines(id), | |||||
| // fetchWarehouseList(), | |||||
| // fetchQcItemCheck(), | |||||
| // ]); | |||||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | |||||
| return <PickOrderDetail consoCode={consoCode}/>; | |||||
| }; | |||||
| PoDetailWrapper.Loading = PickOrderDetailLoading; | |||||
| export default PoDetailWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./PickOrderDetailWrapper" | |||||
| @@ -0,0 +1,91 @@ | |||||
| "use client"; | |||||
| import dayjs from "dayjs"; | |||||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { | |||||
| Dispatch, | |||||
| SetStateAction, | |||||
| useCallback, | |||||
| useEffect, | |||||
| useMemo, | |||||
| useState, | |||||
| } from "react"; | |||||
| import { GridColDef } from "@mui/x-data-grid"; | |||||
| import { CircularProgress, Grid, Typography } from "@mui/material"; | |||||
| import { ByItemsSummary } from "@/app/api/pickorder"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| dayjs.extend(arraySupport); | |||||
| interface Props { | |||||
| rows: ByItemsSummary[] | undefined; | |||||
| setRows: Dispatch<SetStateAction<ByItemsSummary[] | undefined>>; | |||||
| } | |||||
| const ConsolidatePickOrderItemSum: React.FC<Props> = ({ rows, setRows }) => { | |||||
| console.log(rows); | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "name", | |||||
| headerName: "name", | |||||
| flex: 1, | |||||
| renderCell: (params) => { | |||||
| console.log(params.row.name); | |||||
| return params.row.name; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| field: "requiredQty", | |||||
| headerName: "requiredQty", | |||||
| flex: 1, | |||||
| renderCell: (params) => { | |||||
| console.log(params.row.requiredQty); | |||||
| const requiredQty = params.row.requiredQty ?? 0; | |||||
| return `${requiredQty} ${params.row.uomDesc}`; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| field: "availableQty", | |||||
| headerName: "availableQty", | |||||
| flex: 1, | |||||
| renderCell: (params) => { | |||||
| console.log(params.row.availableQty); | |||||
| const availableQty = params.row.availableQty ?? 0; | |||||
| return `${availableQty} ${params.row.uomDesc}`; | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| return ( | |||||
| <Grid | |||||
| container | |||||
| rowGap={1} | |||||
| // direction="column" | |||||
| alignItems="center" | |||||
| justifyContent="center" | |||||
| > | |||||
| <Grid item xs={12}> | |||||
| <Typography variant="h5" marginInlineEnd={2}> | |||||
| {t("Items Included")} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| {!rows ? ( | |||||
| <CircularProgress size={40} /> | |||||
| ) : ( | |||||
| <StyledDataGrid | |||||
| sx={{ maxHeight: 450 }} | |||||
| rows={rows} | |||||
| columns={columns} | |||||
| /> | |||||
| )} | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ConsolidatePickOrderItemSum; | |||||
| @@ -0,0 +1,115 @@ | |||||
| "use client"; | |||||
| import dayjs from "dayjs"; | |||||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { | |||||
| Dispatch, | |||||
| SetStateAction, | |||||
| useCallback, | |||||
| useEffect, | |||||
| useMemo, | |||||
| useState, | |||||
| } from "react"; | |||||
| import { GridColDef, GridInputRowSelectionModel } from "@mui/x-data-grid"; | |||||
| import { Box, CircularProgress, Grid, Typography } from "@mui/material"; | |||||
| import { PickOrderResult } from "@/app/api/pickorder"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| dayjs.extend(arraySupport); | |||||
| interface Props { | |||||
| consoCode: string; | |||||
| rows: Omit<PickOrderResult, "items">[] | undefined; | |||||
| setRows: Dispatch< | |||||
| SetStateAction<Omit<PickOrderResult, "items">[] | undefined> | |||||
| >; | |||||
| revertIds: GridInputRowSelectionModel; | |||||
| setRevertIds: Dispatch<SetStateAction<GridInputRowSelectionModel>>; | |||||
| } | |||||
| const ConsolidatePickOrderSum: React.FC<Props> = ({ | |||||
| consoCode, | |||||
| rows, | |||||
| setRows, | |||||
| revertIds, | |||||
| setRevertIds, | |||||
| }) => { | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "code", | |||||
| headerName: "code", | |||||
| flex: 0.6, | |||||
| }, | |||||
| { | |||||
| field: "pickOrderLines", | |||||
| headerName: "items", | |||||
| flex: 1, | |||||
| renderCell: (params) => { | |||||
| console.log(params); | |||||
| const pickOrderLine = params.row.pickOrderLines as any[]; | |||||
| return ( | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| flexDirection: "column", | |||||
| maxHeight: 100, | |||||
| overflowY: "scroll", | |||||
| scrollbarWidth: "none", // For Firefox | |||||
| "&::-webkit-scrollbar": { | |||||
| display: "none", // For Chrome, Safari, and Opera | |||||
| }, | |||||
| }} | |||||
| > | |||||
| {pickOrderLine.map((item, index) => ( | |||||
| <Grid sx={{mt:1}} | |||||
| key={index} | |||||
| >{`${item.itemName} x ${item.requiredQty} ${item.uomDesc}`}</Grid> // Render each name in a span | |||||
| ))} | |||||
| </Box> | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| return ( | |||||
| <Grid | |||||
| container | |||||
| rowGap={1} | |||||
| // direction="column" | |||||
| alignItems="center" | |||||
| justifyContent="center" | |||||
| > | |||||
| <Grid item xs={12}> | |||||
| <Typography variant="h5" marginInlineEnd={2}> | |||||
| {t("Pick Order Included")} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| {!rows ? ( | |||||
| <CircularProgress size={40} /> | |||||
| ) : ( | |||||
| <StyledDataGrid | |||||
| sx={{ maxHeight: 450 }} | |||||
| checkboxSelection | |||||
| rowSelectionModel={revertIds} | |||||
| onRowSelectionModelChange={(newRowSelectionModel) => { | |||||
| setRevertIds(newRowSelectionModel); | |||||
| }} | |||||
| getRowHeight={(params) => { | |||||
| return 100 | |||||
| }} | |||||
| rows={rows} | |||||
| columns={columns} | |||||
| /> | |||||
| )} | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ConsolidatePickOrderSum; | |||||
| @@ -1,12 +1,372 @@ | |||||
| import { | |||||
| Autocomplete, | |||||
| Box, | |||||
| Button, | |||||
| CircularProgress, | |||||
| FormControl, | |||||
| Grid, | |||||
| Modal, | |||||
| ModalProps, | |||||
| TextField, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { GridToolbarContainer } from "@mui/x-data-grid"; | |||||
| import { | |||||
| FooterPropsOverrides, | |||||
| GridColDef, | |||||
| GridRowSelectionModel, | |||||
| useGridApiRef, | |||||
| } from "@mui/x-data-grid"; | |||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import SearchResults, { | |||||
| Column, | |||||
| defaultPagingController, | |||||
| } from "../SearchResults/SearchResults"; | |||||
| import { | |||||
| ByItemsSummary, | |||||
| ConsoPickOrderResult, | |||||
| PickOrderLine, | |||||
| PickOrderResult, | |||||
| } from "@/app/api/pickorder"; | |||||
| import { useRouter, useSearchParams } from "next/navigation"; | |||||
| import ConsolidatePickOrderItemSum from "./ConsolidatePickOrderItemSum"; | |||||
| import ConsolidatePickOrderSum from "./ConsolidatePickOrderSum"; | |||||
| import { GridInputRowSelectionModel } from "@mui/x-data-grid"; | |||||
| import { | |||||
| fetchConsoDetail, | |||||
| fetchConsoPickOrderClient, | |||||
| releasePickOrder, | |||||
| ReleasePickOrderInputs, | |||||
| } from "@/app/api/pickorder/actions"; | |||||
| import { EditNote } from "@mui/icons-material"; | |||||
| import { fetchNameList, NameList } from "@/app/api/user/actions"; | |||||
| import { useField } from "@mui/x-date-pickers/internals"; | |||||
| import { | |||||
| FormProvider, | |||||
| SubmitErrorHandler, | |||||
| SubmitHandler, | |||||
| useForm, | |||||
| } from "react-hook-form"; | |||||
| import { pickOrderStatusMap } from "@/app/utils/formatUtil"; | |||||
| interface Props { | interface Props { | ||||
| filterArgs: Record<string, any>; | |||||
| } | |||||
| const style = { | |||||
| position: "absolute", | |||||
| top: "50%", | |||||
| left: "50%", | |||||
| transform: "translate(-50%, -50%)", | |||||
| bgcolor: "background.paper", | |||||
| pt: 5, | |||||
| px: 5, | |||||
| pb: 10, | |||||
| width: 1500, | |||||
| }; | |||||
| interface DisableButton { | |||||
| releaseBtn: boolean; | |||||
| removeBtn: boolean; | |||||
| } | } | ||||
| const ConsolidatedPickOrders: React.FC<Props> = ({ | |||||
| const ConsolidatedPickOrders: React.FC<Props> = ({ filterArgs }) => { | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const router = useRouter(); | |||||
| const apiRef = useGridApiRef(); | |||||
| const [filteredPickOrders, setFilteredPickOrders] = useState( | |||||
| [] as ConsoPickOrderResult[] | |||||
| ); | |||||
| const [isLoading, setIsLoading] = useState(false); | |||||
| const [modalOpen, setModalOpen] = useState(false); //change back to false | |||||
| const [consoCode, setConsoCode] = useState<string | undefined>(); ///change back to undefined | |||||
| const [revertIds, setRevertIds] = useState<GridInputRowSelectionModel>([]); | |||||
| const [totalCount, setTotalCount] = useState<number>(); | |||||
| const [usernameList, setUsernameList] = useState<NameList[]>([]); | |||||
| }) => { | |||||
| return <></> | |||||
| } | |||||
| const [byPickOrderRows, setByPickOrderRows] = useState< | |||||
| Omit<PickOrderResult, "items">[] | undefined | |||||
| >(undefined); | |||||
| const [byItemsRows, setByItemsRows] = useState<ByItemsSummary[] | undefined>( | |||||
| undefined | |||||
| ); | |||||
| const [disableRelease, setDisableRelease] = useState<boolean>(true); | |||||
| const formProps = useForm<ReleasePickOrderInputs>(); | |||||
| const errors = formProps.formState.errors; | |||||
| const openDetailModal = useCallback((consoCode: string) => { | |||||
| setConsoCode(consoCode); | |||||
| setModalOpen(true); | |||||
| }, []); | |||||
| const closeDetailModal = useCallback(() => { | |||||
| setModalOpen(false); | |||||
| setConsoCode(undefined); | |||||
| }, []); | |||||
| const onDetailClick = useCallback( | |||||
| (pickOrder: any) => { | |||||
| console.log(pickOrder); | |||||
| const status = pickOrder.status | |||||
| if (pickOrderStatusMap[status] >= 2) { | |||||
| router.push(`/pickorder/detail?consoCode=${pickOrder.consoCode}`); | |||||
| } else { | |||||
| openDetailModal(pickOrder.consoCode); | |||||
| } | |||||
| }, | |||||
| [router, openDetailModal] | |||||
| ); | |||||
| const columns = useMemo<Column<ConsoPickOrderResult>[]>( | |||||
| () => [ | |||||
| { | |||||
| name: "id", | |||||
| label: t("Detail"), | |||||
| onClick: onDetailClick, | |||||
| buttonIcon: <EditNote />, | |||||
| }, | |||||
| { | |||||
| name: "consoCode", | |||||
| label: t("consoCode"), | |||||
| }, | |||||
| { | |||||
| name: "status", | |||||
| label: t("status"), | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const [pagingController, setPagingController] = useState( | |||||
| defaultPagingController | |||||
| ); | |||||
| // pass conso code back to assign | |||||
| // pass user back to assign | |||||
| const fetchNewPageConsoPickOrder = useCallback( | |||||
| async ( | |||||
| pagingController: Record<string, number>, | |||||
| filterArgs: Record<string, number> | |||||
| ) => { | |||||
| setIsLoading(true); | |||||
| const params = { | |||||
| ...pagingController, | |||||
| ...filterArgs, | |||||
| }; | |||||
| const res = await fetchConsoPickOrderClient(params); | |||||
| if (res) { | |||||
| console.log(res); | |||||
| setFilteredPickOrders(res.records); | |||||
| setTotalCount(res.total); | |||||
| } | |||||
| setIsLoading(false); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| useEffect(() => { | |||||
| fetchNewPageConsoPickOrder(pagingController, filterArgs); | |||||
| }, [fetchNewPageConsoPickOrder, pagingController, filterArgs]); | |||||
| const isReleasable = useCallback((itemList: ByItemsSummary[]): boolean => { | |||||
| var isReleasable = true; | |||||
| for (const item of itemList) { | |||||
| isReleasable = item.requiredQty >= item.availableQty; | |||||
| if (!isReleasable) return isReleasable; | |||||
| } | |||||
| return isReleasable; | |||||
| }, []); | |||||
| const fetchConso = useCallback( | |||||
| async (consoCode: string) => { | |||||
| const res = await fetchConsoDetail(consoCode); | |||||
| const nameListRes = await fetchNameList(); | |||||
| if (res) { | |||||
| console.log(res); | |||||
| setByPickOrderRows(res.pickOrders); | |||||
| // for testing | |||||
| // for (const item of res.items) { | |||||
| // item.availableQty = 1000; | |||||
| // } | |||||
| setByItemsRows(res.items); | |||||
| setDisableRelease(isReleasable(res.items)); | |||||
| } else { | |||||
| console.log("error"); | |||||
| console.log(res); | |||||
| } | |||||
| if (nameListRes) { | |||||
| console.log(nameListRes); | |||||
| setUsernameList(nameListRes); | |||||
| } | |||||
| }, | |||||
| [isReleasable] | |||||
| ); | |||||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||||
| (...args) => { | |||||
| closeDetailModal(); | |||||
| // reset(); | |||||
| }, | |||||
| [closeDetailModal] | |||||
| ); | |||||
| const onChange = useCallback( | |||||
| ( | |||||
| event: React.SyntheticEvent, | |||||
| newValue: NameList | |||||
| ) => { | |||||
| console.log(newValue); | |||||
| formProps.setValue("assignTo", newValue.id); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const onSubmit = useCallback<SubmitHandler<ReleasePickOrderInputs & {}>>( | |||||
| async (data, event) => { | |||||
| console.log(data); | |||||
| try { | |||||
| const res = await releasePickOrder(data) | |||||
| console.log(res) | |||||
| if (res.status = 200) { | |||||
| router.push(`/pickorder/detail?consoCode=${data.consoCode}`); | |||||
| } else { | |||||
| throw Error("hv error") | |||||
| } | |||||
| } catch (error) { | |||||
| console.log(error) | |||||
| } | |||||
| }, | |||||
| [releasePickOrder] | |||||
| ); | |||||
| const onSubmitError = useCallback<SubmitErrorHandler<ReleasePickOrderInputs>>( | |||||
| (errors) => {}, | |||||
| [] | |||||
| ); | |||||
| const handleConsolidate_revert = useCallback(() => { | |||||
| console.log(revertIds); | |||||
| }, [revertIds]); | |||||
| useEffect(() => { | |||||
| if (consoCode) { | |||||
| fetchConso(consoCode); | |||||
| formProps.setValue("consoCode", consoCode) | |||||
| } | |||||
| }, [consoCode]); | |||||
| return ( | |||||
| <> | |||||
| <Grid | |||||
| container | |||||
| rowGap={1} | |||||
| // direction="column" | |||||
| alignItems="center" | |||||
| justifyContent="center" | |||||
| > | |||||
| <Grid item xs={12}> | |||||
| {isLoading ? ( | |||||
| <CircularProgress size={40} /> | |||||
| ) : ( | |||||
| <SearchResults<ConsoPickOrderResult> | |||||
| items={filteredPickOrders} | |||||
| columns={columns} | |||||
| pagingController={pagingController} | |||||
| setPagingController={setPagingController} | |||||
| totalCount={totalCount} | |||||
| /> | |||||
| )} | |||||
| </Grid> | |||||
| </Grid> | |||||
| {consoCode != undefined ? ( | |||||
| <Modal open={modalOpen} onClose={closeHandler}> | |||||
| <FormProvider {...formProps}> | |||||
| <Box | |||||
| sx={{ ...style, maxHeight: 800 }} | |||||
| component="form" | |||||
| onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||||
| > | |||||
| <Grid container> | |||||
| <Grid item xs={8}> | |||||
| <Typography mb={2} variant="h4"> | |||||
| {consoCode} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid | |||||
| item | |||||
| xs={4} | |||||
| display="flex" | |||||
| justifyContent="end" | |||||
| alignItems="end" | |||||
| > | |||||
| <FormControl fullWidth> | |||||
| <Autocomplete | |||||
| disableClearable | |||||
| fullWidth | |||||
| getOptionLabel={(option) => option.name} | |||||
| options={usernameList} | |||||
| onChange={onChange} | |||||
| renderInput={(params) => <TextField {...params} />} | |||||
| /> | |||||
| </FormControl> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Box | |||||
| sx={{ | |||||
| height: 400, | |||||
| overflowY: "auto", | |||||
| }} | |||||
| > | |||||
| <Grid container> | |||||
| <Grid item xs={12} sx={{ mt: 2 }}> | |||||
| <ConsolidatePickOrderSum | |||||
| rows={byPickOrderRows} | |||||
| setRows={setByPickOrderRows} | |||||
| consoCode={consoCode} | |||||
| revertIds={revertIds} | |||||
| setRevertIds={setRevertIds} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <ConsolidatePickOrderItemSum | |||||
| rows={byItemsRows} | |||||
| setRows={setByItemsRows} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| <Grid container> | |||||
| <Grid | |||||
| item | |||||
| xs={12} | |||||
| display="flex" | |||||
| justifyContent="end" | |||||
| alignItems="end" | |||||
| > | |||||
| <Button | |||||
| disabled={(revertIds as number[]).length < 1} | |||||
| variant="outlined" | |||||
| onClick={handleConsolidate_revert} | |||||
| sx={{ mr: 1 }} | |||||
| > | |||||
| {t("remove")} | |||||
| </Button> | |||||
| <Button | |||||
| disabled={disableRelease} | |||||
| variant="outlined" | |||||
| // onClick={handleRelease} | |||||
| type="submit" | |||||
| > | |||||
| {t("release")} | |||||
| </Button> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| </Modal> | |||||
| ) : undefined} | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default ConsolidatedPickOrders; | |||||
| export default ConsolidatedPickOrders; | |||||
| @@ -1,24 +1,40 @@ | |||||
| "use client" | |||||
| import { PickOrderResult } from "@/app/api/pickOrder"; | |||||
| "use client"; | |||||
| import { PickOrderResult } from "@/app/api/pickorder"; | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | import { SearchParams } from "@/app/utils/fetchUtil"; | ||||
| import { useCallback, useMemo, useState } from "react"; | import { useCallback, useMemo, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
| import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
| import { flatten, groupBy, intersectionWith, isEmpty, map, sortBy, sortedUniq, uniqBy, upperCase, upperFirst } from "lodash"; | |||||
| import { arrayToDateString, arrayToDayjs, dateStringToDayjs } from "@/app/utils/formatUtil"; | |||||
| import { | |||||
| flatten, | |||||
| groupBy, | |||||
| intersectionWith, | |||||
| isEmpty, | |||||
| map, | |||||
| sortBy, | |||||
| sortedUniq, | |||||
| uniqBy, | |||||
| upperCase, | |||||
| upperFirst, | |||||
| } from "lodash"; | |||||
| import { | |||||
| arrayToDateString, | |||||
| arrayToDayjs, | |||||
| dateStringToDayjs, | |||||
| } from "@/app/utils/formatUtil"; | |||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material"; | import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material"; | ||||
| import PickOrders from "./PickOrders"; | import PickOrders from "./PickOrders"; | ||||
| import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; | |||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| interface Props { | interface Props { | ||||
| pickOrders: PickOrderResult[]; | |||||
| pickOrders: PickOrderResult[]; | |||||
| } | } | ||||
| type SearchQuery = Partial<Omit<PickOrderResult, | |||||
| | "id" | |||||
| | "consoCode" | |||||
| | "completeDate">> | |||||
| type SearchQuery = Partial< | |||||
| Omit<PickOrderResult, "id" | "consoCode" | "completeDate"> | |||||
| >; | |||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| @@ -27,76 +43,134 @@ const PickOrderSearch: React.FC<Props> = ({ | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders) | |||||
| const [tabIndex, setTabIndex] = useState(0); | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); | |||||
| const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | |||||
| const [tabIndex, setTabIndex] = useState(0); | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | |||||
| { label: t("Code"), paramName: "code", type: "text" }, | |||||
| { label: t("Target Date From"), label2: t("Target Date To"), paramName: "targetDate", type: "dateRange" }, | |||||
| { | |||||
| label: t("Type"), paramName: "type", type: "autocomplete", | |||||
| options: sortBy( | |||||
| uniqBy(pickOrders.map((po) => ({ value: po.type, label: t(upperCase(po.type)) })), "value"), | |||||
| "label") | |||||
| }, | |||||
| { | |||||
| label: t("Status"), paramName: "status", type: "autocomplete", | |||||
| options: sortBy( | |||||
| uniqBy(pickOrders.map((po) => ({ value: po.status, label: t(upperFirst(po.status)) })), "value"), | |||||
| "label") | |||||
| }, | |||||
| { | |||||
| label: t("Items"), paramName: "items", type: "autocomplete", // multiple: true, | |||||
| options: uniqBy(flatten(sortBy( | |||||
| pickOrders.map((po) => po.items ? po.items.map((item) => ({ | |||||
| value: item.name, label: item.name, | |||||
| // group: item.type | |||||
| })) : []), | |||||
| "label")), "value") | |||||
| }, | |||||
| ], [t]) | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
| () => [ | |||||
| { label: t("Code"), paramName: "code", type: "text" }, | |||||
| { | |||||
| label: t("Target Date From"), | |||||
| label2: t("Target Date To"), | |||||
| paramName: "targetDate", | |||||
| type: "dateRange", | |||||
| }, | |||||
| { | |||||
| label: t("Type"), | |||||
| paramName: "type", | |||||
| type: "autocomplete", | |||||
| options: sortBy( | |||||
| uniqBy( | |||||
| pickOrders.map((po) => ({ | |||||
| value: po.type, | |||||
| label: t(upperCase(po.type)), | |||||
| })), | |||||
| "value" | |||||
| ), | |||||
| "label" | |||||
| ), | |||||
| }, | |||||
| { | |||||
| label: t("Status"), | |||||
| paramName: "status", | |||||
| type: "autocomplete", | |||||
| options: sortBy( | |||||
| uniqBy( | |||||
| pickOrders.map((po) => ({ | |||||
| value: po.status, | |||||
| label: t(upperFirst(po.status)), | |||||
| })), | |||||
| "value" | |||||
| ), | |||||
| "label" | |||||
| ), | |||||
| }, | |||||
| { | |||||
| label: t("Items"), | |||||
| paramName: "items", | |||||
| type: "autocomplete", // multiple: true, | |||||
| options: uniqBy( | |||||
| flatten( | |||||
| sortBy( | |||||
| pickOrders.map((po) => | |||||
| po.items | |||||
| ? po.items.map((item) => ({ | |||||
| value: item.name, | |||||
| label: item.name, | |||||
| // group: item.type | |||||
| })) | |||||
| : [] | |||||
| ), | |||||
| "label" | |||||
| ) | |||||
| ), | |||||
| "value" | |||||
| ), | |||||
| }, | |||||
| ], | |||||
| [t] | |||||
| ); | |||||
| const onReset = useCallback(() => { | |||||
| setFilteredPickOrders(pickOrders) | |||||
| }, [pickOrders]) | |||||
| const onReset = useCallback(() => { | |||||
| setFilteredPickOrders(pickOrders); | |||||
| }, [pickOrders]); | |||||
| return ( | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilteredPickOrders( | |||||
| pickOrders.filter( | |||||
| (po) => { | |||||
| const poTargetDateStr = arrayToDayjs(po.targetDate) | |||||
| return ( | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilterArgs({ ...query }); // modify later | |||||
| setFilteredPickOrders( | |||||
| pickOrders.filter((po) => { | |||||
| const poTargetDateStr = arrayToDayjs(po.targetDate); | |||||
| // console.log(intersectionWith(po.items?.map(item => item.name), query.items)) | |||||
| return po.code.toLowerCase().includes(query.code.toLowerCase()) | |||||
| && (isEmpty(query.targetDate) || poTargetDateStr.isSame(query.targetDate) || poTargetDateStr.isAfter(query.targetDate)) | |||||
| && (isEmpty(query.targetDateTo) || poTargetDateStr.isSame(query.targetDateTo) || poTargetDateStr.isBefore(query.targetDateTo)) | |||||
| && (intersectionWith(["All"], query.items).length > 0 || intersectionWith(po.items?.map(item => item.name), query.items).length > 0) | |||||
| && (query.status.toLowerCase() == "all" || po.status.toLowerCase().includes(query.status.toLowerCase())) | |||||
| && (query.type.toLowerCase() == "all" || po.type.toLowerCase().includes(query.type.toLowerCase())) | |||||
| } | |||||
| ) | |||||
| ) | |||||
| }} | |||||
| onReset={onReset} | |||||
| /> | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||||
| <Tab label={t("Pick Orders")} iconPosition="end" /> | |||||
| <Tab label={t("Consolidated Pick Orders")} iconPosition="end" /> | |||||
| </Tabs> | |||||
| {tabIndex === 0 && <PickOrders filteredPickOrders={filteredPickOrders}/>} | |||||
| </> | |||||
| ) | |||||
| } | |||||
| // console.log(intersectionWith(po.items?.map(item => item.name), query.items)) | |||||
| return ( | |||||
| po.code.toLowerCase().includes(query.code.toLowerCase()) && | |||||
| (isEmpty(query.targetDate) || | |||||
| poTargetDateStr.isSame(query.targetDate) || | |||||
| poTargetDateStr.isAfter(query.targetDate)) && | |||||
| (isEmpty(query.targetDateTo) || | |||||
| poTargetDateStr.isSame(query.targetDateTo) || | |||||
| poTargetDateStr.isBefore(query.targetDateTo)) && | |||||
| (intersectionWith(["All"], query.items).length > 0 || | |||||
| intersectionWith( | |||||
| po.items?.map((item) => item.name), | |||||
| query.items | |||||
| ).length > 0) && | |||||
| (query.status.toLowerCase() == "all" || | |||||
| po.status | |||||
| .toLowerCase() | |||||
| .includes(query.status.toLowerCase())) && | |||||
| (query.type.toLowerCase() == "all" || | |||||
| po.type.toLowerCase().includes(query.type.toLowerCase())) | |||||
| ); | |||||
| }) | |||||
| ); | |||||
| }} | |||||
| onReset={onReset} | |||||
| /> | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||||
| <Tab label={t("Pick Orders")} iconPosition="end" /> | |||||
| <Tab label={t("Consolidated Pick Orders")} iconPosition="end" /> | |||||
| </Tabs> | |||||
| {tabIndex === 0 && ( | |||||
| <PickOrders | |||||
| filteredPickOrders={filteredPickOrders} | |||||
| filterArgs={filterArgs} | |||||
| /> | |||||
| )} | |||||
| {tabIndex === 1 && <ConsolidatedPickOrders filterArgs={filterArgs} />} | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PickOrderSearch; | |||||
| export default PickOrderSearch; | |||||
| @@ -1,4 +1,4 @@ | |||||
| import { fetchPickOrders } from "@/app/api/pickOrder"; | |||||
| import { fetchPickOrders } from "@/app/api/pickorder"; | |||||
| import GeneralLoading from "../General/GeneralLoading"; | import GeneralLoading from "../General/GeneralLoading"; | ||||
| import PickOrderSearch from "./PickOrderSearch"; | import PickOrderSearch from "./PickOrderSearch"; | ||||
| @@ -10,7 +10,14 @@ const PickOrderSearchWrapper: React.FC & SubComponents = async () => { | |||||
| const [ | const [ | ||||
| pickOrders | pickOrders | ||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchPickOrders() | |||||
| fetchPickOrders({ | |||||
| code: undefined, | |||||
| targetDateFrom: undefined, | |||||
| targetDateTo: undefined, | |||||
| type: undefined, | |||||
| status: undefined, | |||||
| itemName: undefined, | |||||
| }) | |||||
| ]) | ]) | ||||
| return <PickOrderSearch pickOrders={pickOrders}/> | return <PickOrderSearch pickOrders={pickOrders}/> | ||||
| @@ -1,100 +1,157 @@ | |||||
| import { Button, Grid } from "@mui/material"; | |||||
| import { Button, CircularProgress, Grid } from "@mui/material"; | |||||
| import SearchResults, { Column } from "../SearchResults/SearchResults"; | import SearchResults, { Column } from "../SearchResults/SearchResults"; | ||||
| import { PickOrderResult } from "@/app/api/pickOrder"; | |||||
| import { PickOrderResult } from "@/app/api/pickorder"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { useCallback, useMemo, useState } from "react"; | |||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import { isEmpty, upperCase, upperFirst } from "lodash"; | import { isEmpty, upperCase, upperFirst } from "lodash"; | ||||
| import { arrayToDateString } from "@/app/utils/formatUtil"; | import { arrayToDateString } from "@/app/utils/formatUtil"; | ||||
| import { consolidatePickOrder, fetchPickOrderClient } from "@/app/api/pickorder/actions"; | |||||
| import useUploadContext from "../UploadProvider/useUploadContext"; | |||||
| interface Props { | interface Props { | ||||
| filteredPickOrders: PickOrderResult[], | |||||
| filteredPickOrders: PickOrderResult[]; | |||||
| filterArgs: Record<string, any>; | |||||
| } | } | ||||
| const PickOrders: React.FC<Props> = ({ | |||||
| filteredPickOrders | |||||
| }) => { | |||||
| const { t } = useTranslation("pickOrder") | |||||
| const [selectedRows, setSelectedRows] = useState<(string | number)[]>([]); | |||||
| const PickOrders: React.FC<Props> = ({ filteredPickOrders, filterArgs }) => { | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const [selectedRows, setSelectedRows] = useState<(string | number)[]>([]); | |||||
| const [filteredPickOrder, setFilteredPickOrder] = useState( | |||||
| [] as PickOrderResult[] | |||||
| ); | |||||
| const { setIsUploading } = useUploadContext(); | |||||
| const [isLoading, setIsLoading] = useState(false); | |||||
| const [pagingController, setPagingController] = useState({ | |||||
| pageNum: 0, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const [totalCount, setTotalCount] = useState<number>(); | |||||
| const handleConsolidatedRows = useCallback(() => { | |||||
| const handleConsolidatedRows = useCallback(async () => { | |||||
| console.log(selectedRows); | |||||
| setIsUploading(true); | |||||
| try { | |||||
| const res = await consolidatePickOrder(selectedRows as number[]); | |||||
| if (res) { | |||||
| console.log(res); | |||||
| } | |||||
| } catch { | |||||
| setIsUploading(false); | |||||
| } | |||||
| fetchNewPagePickOrder(pagingController, filterArgs); | |||||
| setIsUploading(false); | |||||
| }, [selectedRows, pagingController]); | |||||
| }, [selectedRows]) | |||||
| const fetchNewPagePickOrder = useCallback( | |||||
| async ( | |||||
| pagingController: Record<string, number>, | |||||
| filterArgs: Record<string, number> | |||||
| ) => { | |||||
| setIsLoading(true); | |||||
| const params = { | |||||
| ...pagingController, | |||||
| ...filterArgs, | |||||
| }; | |||||
| const res = await fetchPickOrderClient(params) | |||||
| if (res) { | |||||
| console.log(res); | |||||
| setFilteredPickOrder(res.records); | |||||
| setTotalCount(res.total); | |||||
| } | |||||
| setIsLoading(false); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const columns = useMemo<Column<PickOrderResult>[]>(() => [ | |||||
| { | |||||
| name: "id", | |||||
| label: "", | |||||
| type: "checkbox", | |||||
| disabled: (params) => { | |||||
| return !isEmpty(params.consoCode); | |||||
| } | |||||
| }, | |||||
| { | |||||
| name: "code", | |||||
| label: t("Code"), | |||||
| }, | |||||
| { | |||||
| name: "consoCode", | |||||
| label: t("Consolidated Code"), | |||||
| renderCell: (params) => { | |||||
| return params.consoCode ?? "N/A" | |||||
| } | |||||
| useEffect(() => { | |||||
| fetchNewPagePickOrder(pagingController, filterArgs); | |||||
| }, [fetchNewPagePickOrder, pagingController, filterArgs]); | |||||
| const columns = useMemo<Column<PickOrderResult>[]>( | |||||
| () => [ | |||||
| { | |||||
| name: "id", | |||||
| label: "", | |||||
| type: "checkbox", | |||||
| disabled: (params) => { | |||||
| return !isEmpty(params.consoCode); | |||||
| }, | }, | ||||
| { | |||||
| name: "type", | |||||
| label: t("type"), | |||||
| renderCell: (params) => { | |||||
| return upperCase(params.type) | |||||
| } | |||||
| }, | |||||
| { | |||||
| name: "code", | |||||
| label: t("Code"), | |||||
| }, | |||||
| { | |||||
| name: "consoCode", | |||||
| label: t("Consolidated Code"), | |||||
| renderCell: (params) => { | |||||
| return params.consoCode ?? ""; | |||||
| }, | }, | ||||
| { | |||||
| name: "items", | |||||
| label: t("Items"), | |||||
| renderCell: (params) => { | |||||
| return params.items?.map((i) => i.name).join(", ") | |||||
| } | |||||
| }, | |||||
| { | |||||
| name: "type", | |||||
| label: t("type"), | |||||
| renderCell: (params) => { | |||||
| return upperCase(params.type); | |||||
| }, | }, | ||||
| { | |||||
| name: "targetDate", | |||||
| label: t("Target Date"), | |||||
| renderCell: (params) => { | |||||
| return arrayToDateString(params.targetDate) | |||||
| } | |||||
| }, | |||||
| { | |||||
| name: "items", | |||||
| label: t("Items"), | |||||
| renderCell: (params) => { | |||||
| return params.items?.map((i) => i.name).join(", "); | |||||
| }, | }, | ||||
| { | |||||
| name: "releasedBy", | |||||
| label: t("Released By"), | |||||
| }, | |||||
| { | |||||
| name: "targetDate", | |||||
| label: t("Target Date"), | |||||
| renderCell: (params) => { | |||||
| return arrayToDateString(params.targetDate); | |||||
| }, | }, | ||||
| { | |||||
| name: "status", | |||||
| label: t("Status"), | |||||
| renderCell: (params) => { | |||||
| return upperFirst(params.status) | |||||
| } | |||||
| }, | |||||
| { | |||||
| name: "releasedBy", | |||||
| label: t("Released By"), | |||||
| }, | |||||
| { | |||||
| name: "status", | |||||
| label: t("Status"), | |||||
| renderCell: (params) => { | |||||
| return upperFirst(params.status); | |||||
| }, | }, | ||||
| ], [t]) | |||||
| }, | |||||
| ], | |||||
| [t] | |||||
| ); | |||||
| return ( | |||||
| <Grid container rowGap={1}> | |||||
| <Grid item xs={3}> | |||||
| <Button | |||||
| disabled={selectedRows.length < 1} | |||||
| variant="outlined" | |||||
| > | |||||
| {t("Consolidate")} | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <SearchResults<PickOrderResult> items={filteredPickOrders} columns={columns} pagingController={{ | |||||
| pageNum: 0, | |||||
| pageSize: 0 | |||||
| }} | |||||
| checkboxIds={selectedRows} | |||||
| setCheckboxIds={setSelectedRows} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ) | |||||
| } | |||||
| return ( | |||||
| <Grid container rowGap={1}> | |||||
| <Grid item xs={3}> | |||||
| <Button | |||||
| disabled={selectedRows.length < 1} | |||||
| variant="outlined" | |||||
| onClick={handleConsolidatedRows} | |||||
| > | |||||
| {t("Consolidate")} | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| {isLoading ? ( | |||||
| <CircularProgress size={40} /> | |||||
| ) : ( | |||||
| <SearchResults<PickOrderResult> | |||||
| items={filteredPickOrder} | |||||
| columns={columns} | |||||
| pagingController={pagingController} | |||||
| setPagingController={setPagingController} | |||||
| totalCount={totalCount} | |||||
| checkboxIds={selectedRows!!} | |||||
| setCheckboxIds={setSelectedRows} | |||||
| /> | |||||
| )} | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default PickOrders; | |||||
| export default PickOrders; | |||||
| @@ -152,7 +152,7 @@ const PoSearch: React.FC<Props> = ({ | |||||
| setTotalCount(res.total); | setTotalCount(res.total); | ||||
| } | } | ||||
| }, | }, | ||||
| [fetchPoListClient, pagingController] | |||||
| [fetchPoListClient] | |||||
| ); | ); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -209,7 +209,24 @@ function SearchResults<T extends ResultWithId>({ | |||||
| }; | }; | ||||
| // checkbox | // checkbox | ||||
| const handleRowClick = useCallback((event: MouseEvent<unknown>, id: string | number) => { | |||||
| const handleRowClick = useCallback((event: MouseEvent<unknown>, item: T, columns: Column<T>[]) => { | |||||
| // check is disabled or not | |||||
| var disabled = false | |||||
| columns.forEach((col) => { | |||||
| if (isCheckboxColumn(col) && col.disabled) { | |||||
| disabled = col.disabled(item) | |||||
| if (disabled) { | |||||
| return; | |||||
| } | |||||
| } | |||||
| }) | |||||
| if (disabled) { | |||||
| return; | |||||
| } | |||||
| // set id | |||||
| const id = item.id | |||||
| if (setCheckboxIds) { | if (setCheckboxIds) { | ||||
| const selectedIndex = checkboxIds.indexOf(id); | const selectedIndex = checkboxIds.indexOf(id); | ||||
| let newSelected: (string | number)[] = []; | let newSelected: (string | number)[] = []; | ||||
| @@ -257,7 +274,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
| hover | hover | ||||
| tabIndex={-1} | tabIndex={-1} | ||||
| key={item.id} | key={item.id} | ||||
| onClick={setCheckboxIds ? (event) => handleRowClick(event, item.id) : undefined} | |||||
| onClick={setCheckboxIds? (event) => handleRowClick(event, item, columns) : undefined} | |||||
| role={setCheckboxIds ? "checkbox" : undefined} | role={setCheckboxIds ? "checkbox" : undefined} | ||||
| > | > | ||||
| {columns.map((column, idx) => { | {columns.map((column, idx) => { | ||||
| @@ -1,4 +1,24 @@ | |||||
| { | { | ||||
| "Overview": "概述", | |||||
| "Qc Item": "品質檢驗項目", | |||||
| "Dashboard": "儀表板", | |||||
| "dashboard": "儀表板", | |||||
| "Raw Material": "原料", | |||||
| "Purchase Order": "採購訂單", | |||||
| "Pick Order": "提料單", | |||||
| "View item In-out And inventory Ledger": "存貨", | |||||
| "Inventory": "存貨", | |||||
| "Delivery": "送貨", | |||||
| "Delivery Order": "送貨單", | |||||
| "Scheduling": "生產計劃", | |||||
| "Demand Forecast Setting": "粗排設定", | |||||
| "Demand Forecast": "粗排", | |||||
| "FG & Material Demand Forecast Detail": "成品 & 原料粗排細節", | |||||
| "Detail Scheduling": "細排", | |||||
| "FG Production Schedule": "成品生產計劃", | |||||
| "Settings": "設定", | |||||
| "Edit": "編輯", | |||||
| "Search Criteria": "搜尋條件", | "Search Criteria": "搜尋條件", | ||||
| "All": "全部", | "All": "全部", | ||||
| "No options": "沒有選項", | "No options": "沒有選項", | ||||