"use client"; import { DoResult } from "@/app/api/do"; import { DoSearchAll, fetchDoSearch, releaseDo ,startBatchReleaseAsync, getBatchReleaseProgress} from "@/app/api/do/actions"; import { useRouter } from "next/navigation"; import React, { ForwardedRef, useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Criterion } from "../SearchBox"; import { isEmpty, sortBy, uniqBy, upperFirst } from "lodash"; import { arrayToDateString, arrayToDayjs } from "@/app/utils/formatUtil"; import SearchBox from "../SearchBox/SearchBox"; import { EditNote } from "@mui/icons-material"; import InputDataGrid from "../InputDataGrid"; import { CreateConsoDoInput } from "@/app/api/do/actions"; import { TableRow } from "../InputDataGrid/InputDataGrid"; import { FooterPropsOverrides, GridColDef, GridRowModel, GridToolbarContainer, useGridApiRef, } from "@mui/x-data-grid"; import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm, } from "react-hook-form"; import { Box, Button, Grid, Stack, Typography, TablePagination } from "@mui/material"; import StyledDataGrid from "../StyledDataGrid"; import { GridRowSelectionModel } from "@mui/x-data-grid"; import Swal from "sweetalert2"; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; type Props = { filterArgs?: Record; searchQuery?: Record; onDeliveryOrderSearch: () => void; }; type SearchBoxInputs = Record<"code" | "status" | "estimatedArrivalDate" | "orderDate" | "supplierName" | "shopName" | "deliveryOrderLines" | "codeTo" | "statusTo" | "estimatedArrivalDateTo" | "orderDateTo" | "supplierNameTo" | "shopNameTo" | "deliveryOrderLinesTo" , string>; type SearchParamNames = keyof SearchBoxInputs; // put all this into a new component // ConsoDoForm type EntryError = | { [field in keyof DoResult]?: string; } | undefined; type DoRow = TableRow, EntryError>; const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSearch}) => { const apiRef = useGridApiRef(); const formProps = useForm({ defaultValues: {}, }); const errors = formProps.formState.errors; const { t } = useTranslation("do"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; console.log("🔍 DoSearch - session:", session); console.log("🔍 DoSearch - currentUserId:", currentUserId); const [searchTimeout, setSearchTimeout] = useState(null); const [rowSelectionModel, setRowSelectionModel] = useState([]); const [searchAllDos, setSearchAllDos] = useState([]); const [pagingController, setPagingController] = useState({ pageNum: 1, pageSize: 10, }); const handlePageChange = useCallback((event: unknown, newPage: number) => { const newPagingController = { ...pagingController, pageNum: newPage + 1, }; setPagingController(newPagingController); },[pagingController]); const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { const newPageSize = parseInt(event.target.value, 10); const newPagingController = { pageNum: 1, pageSize: newPageSize, }; setPagingController(newPagingController); }, []); const pagedRows = useMemo(() => { const start = (pagingController.pageNum - 1) * pagingController.pageSize; return searchAllDos.slice(start, start + pagingController.pageSize); }, [searchAllDos, pagingController]); const [currentSearchParams, setCurrentSearchParams] = useState({ code: "", status: "", estimatedArrivalDate: "", orderDate: "", supplierName: "", shopName: "", deliveryOrderLines: "", codeTo: "", statusTo: "", estimatedArrivalDateTo: "", orderDateTo: "", supplierNameTo: "", shopNameTo: "", deliveryOrderLinesTo: "" }); const [hasSearched, setHasSearched] = useState(false); const [hasResults, setHasResults] = useState(false); useEffect(() =>{ setPagingController(p => ({ ...p, pageNum: 1, })); }, [searchAllDos]); //INITIALIZATION useEffect(() => { const loadItems = async () => { try{ //const itemsData = await fetchDoSearch("","","","","","",""); //setSearchAllDos(itemsData); } catch (error){ console.error("Loading Error: ", error); setSearchAllDos([]); }; }; loadItems(); console.log("success"); },[]); const searchCriteria: Criterion[] = useMemo( () => [ { label: t("Code"), paramName: "code", type: "text" }, { label: t("Order Date From"), label2: t("Order Date To"), paramName: "orderDate", type: "dateRange", }, { label: t("Shop Name"), paramName: "shopName", type: "text" }, { label: t("Estimated Arrival From"), label2: t("Estimated Arrival To"), paramName: "estimatedArrivalDate", type: "dateRange", }, { label: t("Status"), paramName: "status", type: "autocomplete", options:[ {label: t('Pending'), value: 'pending'}, {label: t('Receiving'), value: 'receiving'}, {label: t('Completed'), value: 'completed'} ] } ], [t], ); const onReset = useCallback(async () => { try { //const data = await fetchDoSearch("", "", "", "", "","",""); //setSearchAllDos(data); setHasSearched(false); setHasSearched(false); } catch (error) { console.error("Error: ", error); setSearchAllDos([]); } }, []); const onDetailClick = useCallback( (doResult: DoResult) => { router.push(`/do/edit?id=${doResult.id}`); }, [router], ); const validationTest = useCallback( ( newRow: GridRowModel, // rowModel: GridRowSelectionModel ): EntryError => { const error: EntryError = {}; console.log(newRow); // if (!newRow.lowerLimit) { // error["lowerLimit"] = "lower limit cannot be null" // } // if (newRow.lowerLimit && newRow.upperLimit && newRow.lowerLimit > newRow.upperLimit) { // error["lowerLimit"] = "lower limit should not be greater than upper limit" // error["upperLimit"] = "lower limit should not be greater than upper limit" // } return Object.keys(error).length > 0 ? error : undefined; }, [], ); const columns = useMemo( () => [ // { // name: "id", // label: t("Details"), // onClick: onDetailClick, // buttonIcon: , // }, { field: "id", headerName: t("Details"), width: 100, renderCell: (params) => ( ), }, { field: "code", headerName: t("code"), flex: 1.5, }, { field: "shopName", headerName: t("Shop Name"), flex: 1, }, { field: "supplierName", headerName: t("Supplier Name"), flex: 1, }, { field: "orderDate", headerName: t("Order Date"), flex: 1, renderCell: (params) => { return params.row.orderDate ? arrayToDateString(params.row.orderDate) : "N/A"; }, }, { field: "estimatedArrivalDate", headerName: t("Estimated Arrival"), flex: 1, renderCell: (params) => { return params.row.estimatedArrivalDate ? arrayToDateString(params.row.estimatedArrivalDate) : "N/A"; }, }, { field: "status", headerName: t("Status"), flex: 1, renderCell: (params) => { return t(upperFirst(params.row.status)); }, }, ], [t, arrayToDateString], ); const onSubmit = useCallback>( async (data, event) => { const hasErrors = false; console.log(errors); }, [], ); const onSubmitError = useCallback>( (errors) => {}, [], ); //SEARCH FUNCTION const handleSearch = useCallback(async (query: SearchBoxInputs) => { try { setCurrentSearchParams(query); let orderStartDate = query.orderDate; let orderEndDate = query.orderDateTo; let estArrStartDate = query.estimatedArrivalDate; let estArrEndDate = query.estimatedArrivalDateTo; const time = "T00:00:00"; if(orderStartDate != ""){ orderStartDate = query.orderDate + time; } if(orderEndDate != ""){ orderEndDate = query.orderDateTo + time; } if(estArrStartDate != ""){ estArrStartDate = query.estimatedArrivalDate + time; } if(estArrEndDate != ""){ estArrEndDate = query.estimatedArrivalDateTo + time; } let status = ""; if(query.status == "All"){ status = ""; } else{ status = query.status; } const data = await fetchDoSearch( query.code || "", query.shopName || "", status, orderStartDate, orderEndDate, estArrStartDate, estArrEndDate ); setSearchAllDos(data); setHasSearched(true); setHasResults(data.length > 0); } catch (error) { console.error("Error: ", error); } }, []); const debouncedSearch = useCallback((query: SearchBoxInputs) => { if (searchTimeout) { clearTimeout(searchTimeout); } const timeout = setTimeout(() => { handleSearch(query); }, 300); setSearchTimeout(timeout); }, [handleSearch, searchTimeout]); const handleBatchRelease = useCallback(async () => { const selectedIds = rowSelectionModel as number[]; if (!selectedIds.length) return; console.log("🔍 handleBatchRelease - currentUserId:", currentUserId); console.log("🔍 handleBatchRelease - selectedIds:", selectedIds); const result = await Swal.fire({ icon: "question", title: t("Batch Release"), html: `

${t("Selected items on current page")}: ${selectedIds.length}

${t("Total search results")}: ${searchAllDos.length}


${t("Choose release option")}:

`, showCancelButton: true, confirmButtonText: t("Release All Search Results"), cancelButtonText: t("Release Selected Only"), denyButtonText: t("Cancel"), showDenyButton: true, confirmButtonColor: "#8dba00", cancelButtonColor: "#2196f3", denyButtonColor: "#F04438" }); if (result.isDenied) return; let idsToRelease: number[]; if (result.isConfirmed) { idsToRelease = searchAllDos.map(d => d.id); } else { idsToRelease = selectedIds; } try { const startRes = await startBatchReleaseAsync({ ids: idsToRelease, userId: currentUserId ?? 1 }); const jobId = startRes?.entity?.jobId; if (!jobId) { await Swal.fire({ icon: "error", title: t("Error"), text: t("Failed to start batch release") }); return; } await Swal.fire({ title: t("Releasing"), html: `
${t("Total")}: 0
${t("Finished")}: 0
`, allowOutsideClick: false, allowEscapeKey: false, showConfirmButton: false, didOpen: async () => { const update = (total:number, finished:number, success:number, failed:number) => { const bar = document.getElementById("br-bar") as HTMLElement; const pct = total > 0 ? Math.floor((finished / total) * 100) : 0; (document.getElementById("br-total") as HTMLElement).innerText = `${t("Total")}: ${total}`; (document.getElementById("br-finished") as HTMLElement).innerText = `${t("Finished")}: ${finished}`; if (bar) bar.style.width = `${pct}%`; }; const timer = setInterval(async () => { try { const p = await getBatchReleaseProgress(jobId); const e = p?.entity || {}; update(e.total ?? 0, e.finished ?? 0, e.success ?? 0, e.failedCount ?? 0); if (p.code === "FINISHED" || e.running === false) { clearInterval(timer); Swal.close(); // 简化完成提示 - 只显示完成,不显示成功/失败统计 await Swal.fire({ icon: "success", title: t("Completed"), text: t("Batch release completed"), confirmButtonText: t("OK") }); if (currentSearchParams && Object.keys(currentSearchParams).length > 0) { await handleSearch(currentSearchParams); } setRowSelectionModel([]); } } catch (err) { console.error("progress poll error:", err); } }, 800); } }); } catch (error) { console.error("Batch release error:", error); await Swal.fire({ icon: "error", title: t("Error"), text: t("An error occurred during batch release"), confirmButtonText: t("OK") }); } }, [rowSelectionModel, t, currentUserId, searchAllDos, currentSearchParams, handleSearch]); return ( <> {t("Delivery Order")} {/**/} {hasSearched && hasResults && ( )} { setRowSelectionModel(newRowSelectionModel); formProps.setValue("ids", newRowSelectionModel); }} slots={{ footer: FooterToolbar, noRowsOverlay: NoRowsOverlay, }} /> ); }; const FooterToolbar: React.FC = ({ child }) => { return {child}; }; const NoRowsOverlay: React.FC = () => { const { t } = useTranslation("home"); return ( {t("Add some entries!")} ); }; export default DoSearch;