|
- "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<string, any>;
- searchQuery?: Record<string, any>;
- 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<Partial<DoResult>, EntryError>;
-
-
- const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSearch}) => {
- const apiRef = useGridApiRef();
-
- const formProps = useForm<CreateConsoDoInput>({
- 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<NodeJS.Timeout | null>(null);
- const [rowSelectionModel, setRowSelectionModel] =
- useState<GridRowSelectionModel>([]);
-
- const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]);
-
- 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<HTMLInputElement>) => {
- 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<SearchBoxInputs>({
- 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<SearchParamNames>[] = 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<DoRow>,
- // 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<GridColDef[]>(
- () => [
- // {
- // name: "id",
- // label: t("Details"),
- // onClick: onDetailClick,
- // buttonIcon: <EditNote />,
- // },
- {
- field: "id",
- headerName: t("Details"),
- width: 100,
- renderCell: (params) => (
- <Button
- variant="outlined"
- size="small"
- startIcon={<EditNote />}
- onClick={() => onDetailClick(params.row)}
- >
- {t("Details")}
- </Button>
- ),
- },
- {
- 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<SubmitHandler<CreateConsoDoInput>>(
- async (data, event) => {
- const hasErrors = false;
- console.log(errors);
- },
- [],
- );
- const onSubmitError = useCallback<SubmitErrorHandler<CreateConsoDoInput>>(
- (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: `
- <div>
- <p>${t("Selected Shop(s): ")}${searchAllDos.length}</p>
- <p>${t("Selected Item(s): ")}${totalDeliveryOrderLines}</p>
- </div>
- `,
- 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 (
- <>
- <FormProvider {...formProps}>
- <Stack
- spacing={2}
- component="form"
- onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
- >
- <Grid container>
- <Grid item xs={8}>
- <Typography variant="h4" marginInlineEnd={2}>
- {t("Delivery Order")}
- </Typography>
- </Grid>
- <Grid
- item
- xs={4}
- display="flex"
- justifyContent="end"
- alignItems="end"
- >
- <Stack spacing={2} direction="row">
- {/*<Button
- name="submit"
- variant="contained"
- // startIcon={<Check />}
- type="submit"
- >
- {t("Create")}
- </Button>*/}
- {hasSearched && hasResults && (
- <Button
- name="batch_release"
- variant="contained"
- onClick={handleBatchRelease}
- >
- {t("Batch Release")}
- </Button>
- )}
-
- </Stack>
-
- </Grid>
- </Grid>
-
- <SearchBox
- criteria={searchCriteria}
-
- onSearch={handleSearch}
-
- onReset={onReset}
- />
-
- <StyledDataGrid
- rows={pagedRows}
- columns={columns}
- checkboxSelection
- rowSelectionModel={rowSelectionModel}
- onRowSelectionModelChange={(newRowSelectionModel) => {
- setRowSelectionModel(newRowSelectionModel);
- formProps.setValue("ids", newRowSelectionModel);
- }}
- slots={{
- footer: FooterToolbar,
- noRowsOverlay: NoRowsOverlay,
- }}
- />
-
- <TablePagination
- component="div"
- count={searchAllDos.length}
- page={(pagingController.pageNum - 1)}
- rowsPerPage={pagingController.pageSize}
- onPageChange={handlePageChange}
- onRowsPerPageChange={handlePageSizeChange}
- rowsPerPageOptions={[10, 25, 50]}
- />
-
- </Stack>
-
- </FormProvider>
- </>
- );
- };
-
-
- const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
- return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
- };
- const NoRowsOverlay: React.FC = () => {
- const { t } = useTranslation("home");
- return (
- <Box
- display="flex"
- justifyContent="center"
- alignItems="center"
- height="100%"
- >
- <Typography variant="caption">{t("Add some entries!")}</Typography>
- </Box>
- );
- };
-
- export default DoSearch;
|