"use client" import { SearchJoResultRequest, fetchJos, updateJo } from "@/app/api/jo/actions"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Criterion } from "../SearchBox"; import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; import { EditNote } from "@mui/icons-material"; import { arrayToDateString, arrayToDateTimeString, integerFormatter } from "@/app/utils/formatUtil"; import { orderBy, uniqBy, upperFirst } from "lodash"; import SearchBox from "../SearchBox/SearchBox"; import { useRouter } from "next/navigation"; import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; import { StockInLineInput } from "@/app/api/stockIn"; import { JobOrder, JoDetailPickLine, JoStatus } from "@/app/api/jo"; import { Button, Stack } from "@mui/material"; import { BomCombo } from "@/app/api/bom"; import JoCreateFormModal from "./JoCreateFormModal"; import AddIcon from '@mui/icons-material/Add'; import QcStockInModal from "../PoDetail/QcStockInModal"; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import { createStockInLine } from "@/app/api/stockIn/actions"; import { msg } from "../Swal/CustomAlerts"; import dayjs from "dayjs"; import { fetchInventories } from "@/app/api/inventory/actions"; import { InventoryResult } from "@/app/api/inventory"; import { PrinterCombo } from "@/app/api/settings/printer"; interface Props { defaultInputs: SearchJoResultRequest, bomCombo: BomCombo[] printerCombo: PrinterCombo[]; } type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo }) => { const { t } = useTranslation("jo"); const router = useRouter() const [filteredJos, setFilteredJos] = useState([]); const [inputs, setInputs] = useState(defaultInputs); const [pagingController, setPagingController] = useState( defaultPagingController ) const [totalCount, setTotalCount] = useState(0) const [isCreateJoModalOpen, setIsCreateJoModalOpen] = useState(false) // console.log(inputs) const [inventoryData, setInventoryData] = useState([]); const [detailedJos, setDetailedJos] = useState>(new Map()); const fetchJoDetailClient = async (id: number): Promise => { const response = await fetch(`/api/jo/detail?id=${id}`); if (!response.ok) { throw new Error('Failed to fetch JO detail'); } return response.json(); }; useEffect(() => { const fetchDetailedJos = async () => { const detailedMap = new Map(); for (const jo of filteredJos) { try { const detailedJo = await fetchJoDetailClient(jo.id); // Use client function detailedMap.set(jo.id, detailedJo); } catch (error) { console.error(`Error fetching detail for JO ${jo.id}:`, error); } } setDetailedJos(detailedMap); }; if (filteredJos.length > 0) { fetchDetailedJos(); } }, [filteredJos]); useEffect(() => { const fetchInventoryData = async () => { try { const inventoryResponse = await fetchInventories({ code: "", name: "", type: "", pageNum: 0, pageSize: 1000 }); setInventoryData(inventoryResponse.records); } catch (error) { console.error("Error fetching inventory data:", error); } }; fetchInventoryData(); }, []); const getStockAvailable = (pickLine: JoDetailPickLine) => { const inventory = inventoryData.find(inventory => inventory.itemCode === pickLine.code || inventory.itemName === pickLine.name ); if (inventory) { return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty); } return 0; }; const isStockSufficient = (pickLine: JoDetailPickLine) => { const stockAvailable = getStockAvailable(pickLine); return stockAvailable >= pickLine.reqQty; }; const getStockCounts = (jo: JobOrder) => { const detailedJo = detailedJos.get(jo.id); if (!detailedJo?.pickLines || detailedJo.pickLines.length === 0) { return { total: 0, sufficient: 0, insufficient: 0 }; } const totalLines = detailedJo.pickLines.length; const sufficientLines = detailedJo.pickLines.filter(pickLine => isStockSufficient(pickLine)).length; const insufficientLines = totalLines - sufficientLines; return { total: totalLines, sufficient: sufficientLines, insufficient: insufficientLines }; }; const searchCriteria: Criterion[] = useMemo(() => [ { label: t("Code"), paramName: "code", type: "text" }, { label: t("Item Name"), paramName: "itemName", type: "text" }, { label: t("Plan Start"), label2: t("Plan Start To"), paramName: "planStart", type: "datetimeRange" }, ], [t]) const columns = useMemo[]>( () => [ { name: "code", label: t("Code"), flex: 2 }, { name: "item", label: t("Item Code"), renderCell: (row) => { return row.item ? t(row.item.code) : '-' } }, { name: "itemName", label: t("Item Name"), renderCell: (row) => { return row.item ? t(row.item.name) : '-' } }, { name: "reqQty", label: t("Req. Qty"), align: "right", headerAlign: "right", renderCell: (row) => { return integerFormatter.format(row.reqQty) } }, { name: "item", label: t("UoM"), align: "left", headerAlign: "left", renderCell: (row) => { return row.item?.uom ? t(row.item.uom.udfudesc) : '-' } }, { name: "status", label: t("Status"), renderCell: (row) => { // TODO improve return {t(upperFirst(row.status))} } },{ name: "planStart", label: t("Estimated Production Date"), align: "left", headerAlign: "left", renderCell: (row) => { return row.planStart ? arrayToDateTimeString(row.planStart) : '-' } }, { name: "stockStatus" as keyof JobOrder, label: t("BOM Status"), align: "left", headerAlign: "left", renderCell: (row) => { const stockCounts = getStockCounts(row); return ( 0 ? 'red' : 'green' }}> {stockCounts.sufficient}/{stockCounts.total} ); } }, { // TODO put it inside Action Buttons name: "id", label: t("Actions"), // onClick: (record) => onDetailClick(record), // buttonIcon: , renderCell: (row) => { const btnSx = getButtonSx(row); return ( ) } }, ], [inventoryData] ) const handleUpdate = useCallback(async (jo: JobOrder) => { console.log(jo); try { // setIsUploading(true) if (jo.id) { const response = await updateJo({ id: jo.id, status: "storing" }); console.log(`%c Updated JO:`, "color:lime", response); const postData = { itemId: jo?.item?.id!!, acceptedQty: jo?.reqQty ?? 1, productLotNo: jo?.code, productionDate: arrayToDateString(dayjs(), "input"), jobOrderId: jo?.id, // acceptedQty: secondReceiveQty || 0, // acceptedQty: row.acceptedQty, }; const res = await createStockInLine(postData); console.log(`%c Created Stock In Line`, "color:lime", res); msg(t("update success")); refetchData(defaultInputs, "search"); } } catch (e) { // backend error // setServerError(t("An error has occurred. Please try again later.")); console.log(e); } finally { // setIsUploading(false) } }, []) const refetchData = useCallback(async ( query: Record | SearchJoResultRequest, actionType: "reset" | "search" | "paging", ) => { const params: SearchJoResultRequest = { code: query.code, itemName: query.itemName, planStart: query.planStart, planStartTo: query.planStartTo, pageNum: pagingController.pageNum - 1, pageSize: pagingController.pageSize, } const response = await fetchJos(params) if (response) { setTotalCount(response.total); switch (actionType) { case "reset": case "search": setFilteredJos(() => orderBy(response.records, ["id"], ["desc"])); break; case "paging": setFilteredJos((fs) => orderBy(uniqBy([...fs, ...response.records], "id"), ["id"], ["desc"]), ); break; } } }, [pagingController, setPagingController]) const searchDataByPage = useCallback(() => { refetchData(inputs, "paging"); }, [inputs]) useEffect(() => { searchDataByPage(); }, [pagingController]); const getButtonSx = (jo : JobOrder) => { // TODO put it in ActionButtons.ts const joStatus = jo.status?.toLowerCase(); const silStatus = jo.stockInLineStatus?.toLowerCase(); let btnSx = {label:"", color:""}; switch (joStatus) { case "planning": btnSx = {label: t("release jo"), color:"primary.main"}; break; case "pending": btnSx = {label: t("scan picked material"), color:"error.main"}; break; case "processing": btnSx = {label: t("complete jo"), color:"warning.main"}; break; // case "packaging": // case "storing": btnSx = {label: t("view putaway"), color:"secondary.main"}; break; case "storing": switch (silStatus) { case "pending": btnSx = {label: t("process epqc"), color:"success.main"}; break; case "received": btnSx = {label: t("view putaway"), color:"secondary.main"}; break; case "escalated": if (sessionToken?.id == jo.silHandlerId) { btnSx = {label: t("escalation processing"), color:"warning.main"}; break; } default: btnSx = {label: t("view stockin"), color:"info.main"}; } break; case "completed": btnSx = {label: t("view stockin"), color:"info.main"}; break; default: btnSx = {label: t("scan picked material"), color:"success.main"}; } return btnSx }; const { data: session } = useSession(); const sessionToken = session as SessionWithTokens | null; const [openModal, setOpenModal] = useState(false); const [modalInfo, setModalInfo] = useState(); const onDetailClick = useCallback((record: JobOrder) => { if (record.status == "processing") { handleUpdate(record) } else if (record.status == "storing" || record.status == "completed") { if (record.stockInLineId != null) { const data = { id: record.stockInLineId, expiryDate: arrayToDateString(dayjs().add(1, "month"), "input"), } setModalInfo(data); setOpenModal(true); } else { alert('Invalid Stock In Line Id'); } } else { router.push(`/jo/edit?id=${record.id}`) } }, []) const closeNewModal = useCallback(() => { // const response = updateJo({ id: 1, status: "storing" }); setOpenModal(false); // Close the modal first // setTimeout(() => { // }, 300); // Add a delay to avoid immediate re-trigger of useEffect refetchData(defaultInputs, "search"); }, []); const onSearch = useCallback((query: Record) => { setInputs(() => ({ code: query.code, itemName: query.itemName, planStart: query.planStart, planStartTo: query.planStartTo })) refetchData(query, "search"); }, []) const onReset = useCallback(() => { refetchData(defaultInputs, "paging"); }, []) // Manual Create Jo Related const onOpenCreateJoModal = useCallback(() => { setIsCreateJoModalOpen(() => true) }, []) const onCloseCreateJoModal = useCallback(() => { setIsCreateJoModalOpen(() => false) }, []) return <> items={filteredJos} columns={columns} setPagingController={setPagingController} pagingController={pagingController} totalCount={totalCount} // isAutoPaging={false} /> } export default JoSearch;