"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]); 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"), //label2: t("Estimated Arrival To"), paramName: "estimatedArrivalDate", type: "date", }, { 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 { setSearchAllDos([]); setHasSearched(false); setHasResults(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 = ""; let orderEndDate = ""; let estArrStartDate = query.estimatedArrivalDate; let estArrEndDate = query.estimatedArrivalDate; 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.estimatedArrivalDate + 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); setSearchAllDos([]); setHasSearched(true); setHasResults(false); } }, []); const debouncedSearch = useCallback((query: SearchBoxInputs) => { if (searchTimeout) { clearTimeout(searchTimeout); } const timeout = setTimeout(() => { handleSearch(query); }, 300); setSearchTimeout(timeout); }, [handleSearch, searchTimeout]); const handleBatchRelease = useCallback(async () => { const totalDeliveryOrderLines = searchAllDos.reduce((sum, doItem) => { return sum + (doItem.deliveryOrderLines?.length || 0); }, 0); const result = await Swal.fire({ icon: "question", title: t("Batch Release"), html: `

${t("Selected Shop(s): ")}${searchAllDos.length}

${t("Selected Item(s): ")}${totalDeliveryOrderLines}

`, showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), confirmButtonColor: "#8dba00", cancelButtonColor: "#F04438" }); if (result.isConfirmed) { const idsToRelease = searchAllDos.map(d => d.id); 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; } const progressSwal = Swal.fire({ title: t("Releasing"), text: "0% (0 / 0)", allowOutsideClick: false, allowEscapeKey: false, showConfirmButton: false, didOpen: () => { Swal.showLoading(); } }); const timer = setInterval(async () => { try { const p = await getBatchReleaseProgress(jobId); const e = p?.entity || {}; const total = e.total ?? 0; const finished = e.finished ?? 0; const percentage = total > 0 ? Math.round((finished / total) * 100) : 0; const textContent = document.querySelector('.swal2-html-container'); if (textContent) { textContent.textContent = `${percentage}% (${finished} / ${total})`; } if (p.code === "FINISHED" || e.running === false) { clearInterval(timer); await new Promise(resolve => setTimeout(resolve, 500)); Swal.close(); await Swal.fire({ icon: "success", title: t("Completed"), text: t("Batch release completed successfully."), confirmButtonText: t("Confirm"), confirmButtonColor: "#8dba00" }); if (currentSearchParams && Object.keys(currentSearchParams).length > 0) { await handleSearch(currentSearchParams); } } } 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") }); }} }, [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;